使用 Buck2 和 Reindeer 生成 Rust 项目 BUCK

不管是 Buck2 还是 Bazel 构建 Rust 工程时都没有使用 Cargo.toml 文件, 这就意味着需要手动维护依赖关系, 这对于 Rust 项目来说是一个非常大的挑战。 Meta 提供的 Reindeer 可以帮助自动生成 BUCK 文件, 可以解决这部分的问题。

在示例工程中加入 rand 依赖

首先在项目中的 Cargo.toml 文件中加入 rand 依赖, 在 src/main.rs 文件中使用 rand 生成一个随机数。

use rand::{thread_rng, Rng};

fn main() {
    let mut rng = thread_rng();
    let x: u32 = rng.gen();

    println!("A random value: {}", x);
}

虽然是使用 cargo build 或者 cargo run 命令都可以正常执行, 但是如果直接使用 buck2 build 命令构建项目, 会报错:

$ buck2 build //:rust-buck2
File changed: root//Cargo.toml
File changed: root//Cargo.lock
File changed: root//target/debug/.fingerprint/rust-buck2-fe6911eada5a5db6
387 additional file change events
Action failed: root//:rust-buck2 (rustc bin-pic-static_pic-link/rust_buck2-link bin,pic,link [diag])
Local command returned non-zero exit code 1
Reproduce locally: `env -- 'BUCK_SCRATCH_PATH=buck-out/v2/tmp/root/524f8da68ea2a374/__rust-buck2__/rustc/_buck_5339cff94 ...<omitted>... ck-out/v2/gen/root/524f8da68ea2a374/__rust-buck2__/bin-pic-static_pic-link/rust_buck2-link-diag.args (run `buck2 log what-failed` to get the full command)`
stdout:
stderr:
error[E0432]: unresolved import `rand`
 --> ./src/main.rs:1:5
  |
1 | use rand::{thread_rng, Rng};
  |     ^^^^ use of undeclared crate or module `rand`


error: aborting due to 1 previous error


For more information about this error, try `rustc --explain E0432`.

Build ID: 41b05c41-4e1d-45f2-a35b-ca2bb7ac0565
Jobs completed: 6. Time elapsed: 0.0s.
Cache hits: 0%. Commands: 1 (cached: 0, remote: 0, local: 1)
BUILD FAILED
Failed to build 'root//:rust-buck2 (prelude//platforms:default#524f8da68ea2a374)'

说明此时 Buck2 并不知道如何处理 rand 依赖。

使用 Reindeer 生成 BUCK 文件

首先在系统中安装 Reindeer

$ cargo install --locked --git https://github.com/facebookincubator/reindeer reindeer

根据 Reindeer 的文档, 可以使用 reindeer buckify 命令生成 BUCK 文件。

$ reindeer buckify

这时候发现 BUCK 文件中多了很多内容, 但是之前的 rust_binaryrust-buck2 任务被移除了,

# @generated by `reindeer buckify`

load("@prelude//rust:cargo_buildscript.bzl", "buildscript_run")
load("@prelude//rust:cargo_package.bzl", "cargo")

http_archive(
    name = "cfg-if-1.0.0.crate",
    sha256 = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd",
    strip_prefix = "cfg-if-1.0.0",
    urls = ["https://crates.io/api/v1/crates/cfg-if/1.0.0/download"],
    visibility = [],
)

cargo.rust_library(
    name = "cfg-if-1.0.0",
    srcs = [":cfg-if-1.0.0.crate"],
    crate = "cfg_if",
    crate_root = "cfg-if-1.0.0.crate/src/lib.rs",
    edition = "2018",
    visibility = [],
)

http_archive(
    name = "getrandom-0.2.12.crate",
    sha256 = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5",
    strip_prefix = "getrandom-0.2.12",
    urls = ["https://crates.io/api/v1/crates/getrandom/0.2.12/download"],
    visibility = [],
)

cargo.rust_library(
    name = "getrandom-0.2.12",
    srcs = [":getrandom-0.2.12.crate"],
    crate = "getrandom",
    crate_root = "getrandom-0.2.12.crate/src/lib.rs",
    edition = "2018",
    features = ["std"],
    platform = {
        "linux-arm64": dict(
            deps = [":libc-0.2.153"],
        ),
        "linux-x86_64": dict(
            deps = [":libc-0.2.153"],
        ),
        "macos-arm64": dict(
            deps = [":libc-0.2.153"],
        ),
        "macos-x86_64": dict(
            deps = [":libc-0.2.153"],
        ),
    },
    visibility = [],
    deps = [":cfg-if-1.0.0"],
)

http_archive(
    name = "libc-0.2.153.crate",
    sha256 = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd",
    strip_prefix = "libc-0.2.153",
    urls = ["https://crates.io/api/v1/crates/libc/0.2.153/download"],
    visibility = [],
)

cargo.rust_library(
    name = "libc-0.2.153",
    srcs = [":libc-0.2.153.crate"],
    crate = "libc",
    crate_root = "libc-0.2.153.crate/src/lib.rs",
    edition = "2015",
    visibility = [],
)

http_archive(
    name = "ppv-lite86-0.2.17.crate",
    sha256 = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de",
    strip_prefix = "ppv-lite86-0.2.17",
    urls = ["https://crates.io/api/v1/crates/ppv-lite86/0.2.17/download"],
    visibility = [],
)

cargo.rust_library(
    name = "ppv-lite86-0.2.17",
    srcs = [":ppv-lite86-0.2.17.crate"],
    crate = "ppv_lite86",
    crate_root = "ppv-lite86-0.2.17.crate/src/lib.rs",
    edition = "2018",
    features = [
        "simd",
        "std",
    ],
    visibility = [],
)

alias(
    name = "rand",
    actual = ":rand-0.8.5",
    visibility = ["PUBLIC"],
)

http_archive(
    name = "rand-0.8.5.crate",
    sha256 = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404",
    strip_prefix = "rand-0.8.5",
    urls = ["https://crates.io/api/v1/crates/rand/0.8.5/download"],
    visibility = [],
)

cargo.rust_library(
    name = "rand-0.8.5",
    srcs = [":rand-0.8.5.crate"],
    crate = "rand",
    crate_root = "rand-0.8.5.crate/src/lib.rs",
    edition = "2018",
    features = [
        "alloc",
        "default",
        "getrandom",
        "libc",
        "rand_chacha",
        "std",
        "std_rng",
    ],
    platform = {
        "linux-arm64": dict(
            deps = [":libc-0.2.153"],
        ),
        "linux-x86_64": dict(
            deps = [":libc-0.2.153"],
        ),
        "macos-arm64": dict(
            deps = [":libc-0.2.153"],
        ),
        "macos-x86_64": dict(
            deps = [":libc-0.2.153"],
        ),
    },
    visibility = [],
    deps = [
        ":rand_chacha-0.3.1",
        ":rand_core-0.6.4",
    ],
)

http_archive(
    name = "rand_chacha-0.3.1.crate",
    sha256 = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88",
    strip_prefix = "rand_chacha-0.3.1",
    urls = ["https://crates.io/api/v1/crates/rand_chacha/0.3.1/download"],
    visibility = [],
)

cargo.rust_library(
    name = "rand_chacha-0.3.1",
    srcs = [":rand_chacha-0.3.1.crate"],
    crate = "rand_chacha",
    crate_root = "rand_chacha-0.3.1.crate/src/lib.rs",
    edition = "2018",
    features = ["std"],
    visibility = [],
    deps = [
        ":ppv-lite86-0.2.17",
        ":rand_core-0.6.4",
    ],
)

http_archive(
    name = "rand_core-0.6.4.crate",
    sha256 = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c",
    strip_prefix = "rand_core-0.6.4",
    urls = ["https://crates.io/api/v1/crates/rand_core/0.6.4/download"],
    visibility = [],
)

cargo.rust_library(
    name = "rand_core-0.6.4",
    srcs = [":rand_core-0.6.4.crate"],
    crate = "rand_core",
    crate_root = "rand_core-0.6.4.crate/src/lib.rs",
    edition = "2018",
    features = [
        "alloc",
        "getrandom",
        "std",
    ],
    visibility = [],
    deps = [":getrandom-0.2.12"],
)

只能在 BUCK 文件中手动加入 rust_binary 任务, 并且加入 rand 依赖。

rust_binary(
    name = "rust-buck2",
    srcs = glob(["src/**/*.rs"]),
    crate_root = "src/main.rs",
    deps = [
        ":rand",
    ],
)

再次使用 buck2 build 命令构建项目或 buck2 run 命令运行项目, 都可以正常执行,

$ buck2 build //:rust-buck2
Build ID: 6b33c530-8f47-4c65-84d9-08586c0b56fa
Network: Up: 0B  Down: 910KiB
Jobs completed: 154. Time elapsed: 21.6s.
Cache hits: 0%. Commands: 21 (cached: 0, remote: 0, local: 21)
BUILD SUCCEEDED
$ buck2 run //:rust-buck2
Build ID: 5ac5973f-423a-4248-be29-2935bdfdb606
Network: Up: 0B  Down: 0B
Jobs completed: 3. Time elapsed: 0.0s.
BUILD SUCCEEDED
A random value: 3295093910

deps, alias, http_archive 和 cargo.rust_library

alias(
    name = "rand",
    actual = ":rand-0.8.5",
    visibility = ["PUBLIC"],
)

BUCK 文件中可以看到一个 alias 指向了 rand-0.8.5 这个 cargo.rust_library, 如果把 rust_binary 任务中的 :rand 替换成 :rand-0.8.5 也是可以正常执行的。 这里的 visibility 是指定了 alias 的可见性, 如果不指定的话默认是 PUBLIC

cargo.rust_library(
    name = "rand-0.8.5",
    srcs = [":rand-0.8.5.crate"],
    crate = "rand",
    crate_root = "rand-0.8.5.crate/src/lib.rs",
    edition = "2018",
    features = [
        "alloc",
        "default",
        "getrandom",
        "libc",
        "rand_chacha",
        "std",
        "std_rng",
    ],
    platform = {
        "linux-arm64": dict(
            deps = [":libc-0.2.153"],
        ),
        "linux-x86_64": dict(
            deps = [":libc-0.2.153"],
        ),
        "macos-arm64": dict(
            deps = [":libc-0.2.153"],
        ),
        "macos-x86_64": dict(
            deps = [":libc-0.2.153"],
        ),
    },
    visibility = [],
    deps = [
        ":rand_chacha-0.3.1",
        ":rand_core-0.6.4",
    ],
)
  1. cargo.rust_library 是文件头 load("@prelude//rust:cargo_package.bzl", "cargo") 中定义的一个函数, 用于指定一个 Rust 依赖库,可以在工程目录下 prelude/rust/cargo_package.bzl 文件中找到这个函数的定义。
  2. alias 的引入为 crate 升级提供了便利, 同时代码中的 deps 也可以更加清晰, 和 Cargo.toml 文件中的 dependencies 更加匹配, 让 Rust 开发者更加容易理解。
http_archive(
    name = "rand-0.8.5.crate",
    sha256 = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404",
    strip_prefix = "rand-0.8.5",
    urls = ["https://crates.io/api/v1/crates/rand/0.8.5/download"],
    visibility = [],
)

http_archive 是一个 Buck2 的内置 Rule, 用于从网络上下载文件, 这里用于下载 rand-0.8.5 这个 crate 的源码, 并且指定了 sha256 值, 用于校验下载的文件是否正确。


测试的项目在 GitHub - Rust Buck2 Example , 后续会持续更新。

Rust Monorepo Template with Buck2 中, 正在持续尝试构建一个完整的 Rust Monorepo 工程模板, 通过 Buck2Reindeer 管理整个工程的构建和测试, 可以从这个项目中开始你的 Rust Monorepo 之旅。