Cargo 与 Bazel:两种构建系统在 Rust 大型项目中的取舍分析

Cargo 与 Bazel:两种构建系统在 Rust 大型项目中的取舍分析

一、Cargo:够用就好

Cargo 是 Rust 的官方构建系统,对纯 Rust 项目来说几乎完美:

# Cargo.toml —— 简洁直观的配置
[package]
name = "my-service"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# 一个 cargo build 搞定一切

Cargo 的优势很明显:

  • 零配置入门cargo new 就能开始写代码。
  • crates.io 生态:依赖管理自动、版本解析可靠。
  • 项目结构约定src/main.rssrc/lib.rs 等约定让所有 Rust 项目看起来一样。
  • 增量编译cargo build 自动只编译修改过的部分。

但 Cargo 也有局限:

  • 只懂 Rust:无法管理 C++、Python、Protobuf 等非 Rust 代码。
  • 单仓库支持弱:monorepo 场景下,跨 crate 的依赖处理不够灵活。
  • 缓存粒度粗:没有分布式构建缓存(虽然有 sccache,但不是内建的)。

二、Bazel:为大型多语言项目而生

Bazel 是 Google 开源的构建系统,支持几十种语言,核心卖点是多语言、增量编译、分布式缓存

一个典型的 Bazel Rust 项目结构如下:

# WORKSPACE —— 定义外部依赖和工作空间
workspace(name = "my_project")

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

# 导入 Rust 规则
http_archive(
    name = "rules_rust",
    sha256 = "...",
    urls = ["https://github.com/bazelbuild/rules_rust/releases/download/..."],
)

load("@rules_rust//rust:repositories.bzl", "rules_rust_dependencies")
rules_rust_dependencies()

# 导入 crates.io 依赖(Bazel 也可以拉 Rust 的包)
load("@rules_rust//crate_universe:defs.bzl", "crates_repository")

crates_repository(
    name = "crates_index",
    cargo_lockfile = "//:Cargo.lock",  # 复用 Cargo.lock
    manifests = ["//:Cargo.toml"],
)
# BUILD.bazel —— 定义构建目标(每个目录一个 BUILD 文件)
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")

# 定义 Rust 库
rust_library(
    name = "my_lib",
    srcs = ["src/lib.rs"],
    deps = [
        "@crates_index//:tokio",    # 引用外部 crate
        "@crates_index//:serde",
    ],
    visibility = ["//visibility:public"],  # 控制可见性
)

# 定义 Rust 二进制
rust_binary(
    name = "my_service",
    srcs = ["src/main.rs"],
    deps = [
        ":my_lib",                   # 引用同项目的库
        "//proto:greeter_rust_proto", # 引用 Protobuf 生成的 Rust 代码
        "@crates_index//:tonic",
    ],
)

Bazel 的真正威力在多语言项目中体现:

# proto/BUILD.bazel —— 一个目录同时管理多种语言的生成代码
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@rules_rust//proto:defs.bzl", "rust_proto_library")

# Protobuf 定义(语言无关)
proto_library(
    name = "greeter_proto",
    srcs = ["greeter.proto"],
    visibility = ["//visibility:public"],
)

# 自动生成 Rust 代码
rust_proto_library(
    name = "greeter_rust_proto",
    deps = [":greeter_proto"],
)

# 自动生成 Python 代码
load("@rules_python//python:defs.bzl", "py_proto_library")
py_proto_library(
    name = "greeter_py_proto",
    deps = [":greeter_proto"],
)

三、核心取舍对比

我把两种构建系统在大型项目中的关键差异总结如下:

维度CargoBazel大项目影响
学习曲线低(30 分钟入门)高(需要学 Starlark、BUILD 语法)团队培训成本
配置复杂度Cargo.toml 一个文件WORKSPACE + 每个目录 BUILD.bazel维护负担
增量编译自动(crate 粒度)精确(文件级别,更细粒度)CI 时间
分布式缓存需要 sccache内建 Remote Cache + Remote Execution编译速度
多语言支持仅 RustRust/C++/Python/Java/Go/Proto…跨语言项目必需
外部依赖crates.io (版本好管理)需要手动声明或通过 crate_universe依赖管理复杂度
IDE 支持rust-analyzer 完美需要生成 Cargo.toml 代理开发体验
CI 集成简单(cargo test复杂(bazel 配置 + 缓存服务)CI 维护成本

四、什么时候该切换到 Bazel?

我的判断标准很简单,满足以下任意两条就应该考虑 Bazel:

  1. 项目超过 3 种编程语言:如果你在写 Rust 服务 + C++ FFI + Python 脚本 + Protobuf 定义,Cargo 管不了这些。
  2. 团队超过 10 个开发者:Bazel 的可见性控制(visibility)、构建目标粒度和缓存共享对于大团队更有价值。
  3. CI 编译时间超过 10 分钟:Bazel 的分布式缓存(Remote Cache)可以让第二次及以后的全量构建变成几乎即时的。
  4. 单仓库(monorepo)中有多个独立服务:Bazel 的精确依赖分析能确保"只重新编译真正需要的东西"。

如果你只是一个 Rust 项目,没有其他语言,团队不超过 5 个人,别折腾 Bazel——Cargo 完全够用,换 Bazel 反而会增加维护负担。

实际项目中,如果你已经用 Cargo 开发了一段时间想迁移到 Bazel,可以借助 rules_rustcrate_universe 直接读取现有的 Cargo.tomlCargo.lock,减少迁移成本:

# 直接读取 Cargo.toml 中的依赖,生成 Bazel 的 BUILD 文件
crates_repository(
    name = "crates_index",
    cargo_lockfile = "//:Cargo.lock",
    manifests = ["//:Cargo.toml", "//sub_crate:Cargo.toml"],
)

实际迁移时还踩过一个坑:crate_universe 生成的 BUILD 文件不包含 build.rs 的编译产物,导致依赖 OpenSSL 的 crate 在 Bazel 下直接编译失败,需要手动补 cargo_build_script 规则。

五、总结

  1. Cargo——Rust 专属、简单直观、生态完善。纯 Rust 项目首选。
  2. Bazel——多语言、增量精确、分布式缓存。大团队多语言项目的最佳选择。
  3. 迁移成本——如果未来可能上 Bazel,建议一开始就把 Cargo.toml 和目录结构调整好,减少后续迁移的工作量。
  4. 混合使用——也可以在 Bazel 项目中让 Bazel 管理外部依赖,内部 Rust 代码继续用 cargo build 开发(通过 Bazel 的 genrule 桥接)。

作为程序员,接触 Bazel 的过程确实比 Cargo 痛苦很多——Starlark 语法、WORKSPACE 配置、BUILD 文件规则……每一个概念都值得专门写一篇文章。但理解了它的设计理念后,不得不承认:在大型多语言项目中,Bazel 的复杂度是有道理的,因为它解决的问题本身就是复杂的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值