使用 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 , 第一段说明了 Buck 、 Buck2 和 Bazel 存在的关键原因, 就是为了公司内部的 Monorepo 提供更好的构建和测试支持。
采用 Monorepo 的方式组织大型 Rust 工程, 可以替代 Cargo Workspaces 。 当然更不推荐的模式包括 Git Submodules、 Git Subtree 或者多个代码仓库(Polyrepo)的方式, 核心的关键是要把所有的代码都组织在一起, 在任何代码的提交后马上进行整个工程的构建和测试, 并且让所有开发者都有代码的可见性。
Mega 项目目前在整体个工程组织中就存在严重的问题, 目前所做的研究是为了按照 Monorepo 的方式重新组织工程做的准备。
根据 Reindeer 中 Example 的设计, 使用以下目录结构来组织代码:
├── project
├── third-party
│ ├── fixups
│ ├── macros
│ ├── top
│ └── vendor
└── toolchains
所有开发的工程都可以放到 project 中, 项目的依赖通过 Reindeer 在 third-party 中进行管理。 如果工程中还有其他语言的项目, 可以在 third-party 中根据语言构建新的目录, 例如 third-party/python、 third-party/swift 等。 project 下的目录可以根据工程层次进行划分, 层级并没有限制。
在 project 中使用的 crates 在 third-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 中的 rand 和 semver 两个 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工程模板, 通过Buck2和Reindeer管理整个工程的构建和测试, 可以从这个项目中开始你的Rust Monorepo之旅。
