使用 Buck2 和 Reindeer 构建 Rust Monorepo 工程

Meta employs a very large monorepo, consisting of a variety of programming languages, including C++, Python, Rust, Kotlin, Swift, Objective-C, Haskell, OCaml, and more. Google employs a different but functionally similar monorepo.

Buck2 文档中有一篇 Why , 第一段说明了 BuckBuck2Bazel 存在的关键原因, 就是为了公司内部的 Monorepo 提供更好的构建和测试支持。

采用 Monorepo 的方式组织大型 Rust 工程, 可以替代 Cargo Workspaces 。 当然更不推荐的模式包括 Git SubmodulesGit Subtree 或者多个代码仓库(Polyrepo)的方式, 核心的关键是要把所有的代码都组织在一起, 在任何代码的提交后马上进行整个工程的构建和测试, 并且让所有开发者都有代码的可见性。

Mega 项目目前在整体个工程组织中就存在严重的问题, 目前所做的研究是为了按照 Monorepo 的方式重新组织工程做的准备。

根据 ReindeerExample 的设计, 使用以下目录结构来组织代码:

├── project
├── third-party
│   ├── fixups
│   ├── macros
│   ├── top
│   └── vendor
└── toolchains

所有开发的工程都可以放到 project 中, 项目的依赖通过 Reindeerthird-party 中进行管理。 如果工程中还有其他语言的项目, 可以在 third-party 中根据语言构建新的目录, 例如 third-party/pythonthird-party/swift 等。 project 下的目录可以根据工程层次进行划分, 层级并没有限制。

project 中使用的 cratesthird-party 目录的 Cargo.toml 进行管理,

[dependencies]
rand = '0.8.5'
semver = '1.0.21'

执行 Reindeer 后在 third-party 更新 BUCK 文件。

$ reindeer --third-party-dir third-party vendor
$ reindeer --third-party-dir third-party buckify

BUCK 文件中产生的 alias 是暴露给 project 使用, 例如 third-party 中的 randsemver 两个 alias

alias(
    name = "semver",
    actual = ":semver-1.0.21",
    visibility = ["PUBLIC"],
)

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

project 中项目的 BUCK 文件中可以直接使用 third-party 中的 alias

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

当然也可以使用 //projects/rust-library:rust-library 的方式来引用 project 中的 library

虽然使用 Reindeer 通过 third-party 维护 crates 的依赖比较麻烦, 但在大型公司内部都对 third-party 有严格的使用要求,除了安全、性能等原因,还有合规方面的很多考量。 通过对 third-party 的控制, 可以有效的管理开发者对外部 crates 的使用, 提升整个工程流程的可控性。


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