使用 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
之旅。