bacon:替代 cargo-watch 的工具
bacon:替代 cargo-watch 的 Rust 监控工具实战指南(含 workspace / hook / 精准 watch)
Rust 生态里最常见的“文件变更自动跑命令”方案长期是 cargo-watch。但它的上游仓库已经被归档为只读(archived),不少团队开始寻找更活跃、更“工程化”的替代品。(GitHub)
bacon 的定位是“后台代码检查器”:把“要跑什么命令、监听哪些文件、失败/成功后做什么动作”都收敛到一套可维护的 jobs 配置里,适合单项目,也适合 workspace/monorepo。(GitHub)
1. 快速开始:安装与第一条命令
安装:
cargo install --locked bacon
进入项目根目录运行:
bacon
不带参数时会跑默认 job(通常是 check),并持续监听文件变化触发重跑。(Dystroy)
如果你想把配置放进仓库并共享给团队,可以在项目里初始化:
bacon --init
初始化会生成项目级 bacon.toml,官方也建议把它纳入版本控制,随着项目演进逐步调整。(GitHub)
2. bacon 的核心抽象:Jobs + 配置加载顺序
2.1 Job 是什么?
在 bacon 里,一切围绕 job:
- 一条命令(通常是
cargo check/cargo test/cargo run/clippy等) - 一套 watch/ignore 策略(哪些文件变化触发、哪些忽略)
- 可选的成功/失败 hook(例如失败播放提示音、成功切回上一个 job、导出诊断等)
仓库 README 的例子里就展示了用 job 自定义 targets/examples 等检查命令。(GitHub)
2.2 配置文件放哪?workspace 和 package 怎么覆盖?
bacon 会按顺序加载多层配置,后加载的覆盖先加载的(包括默认内置配置、全局 prefs、workspace/package 的 metadata 与 bacon.toml、环境变量指定配置、以及 --config-toml 注入内容等)。(Dystroy)
这对 monorepo 的意义是:
- 根目录放 workspace 级
bacon.toml:定义“全局 job + 常用 crate job” - 特定 crate 需要差异化时,再在 crate 目录补一个更小的
bacon.toml(只写差异)
3. 你最关心的问题:怎么像 cargo watch -- --xx -xx 那样传参?
bacon cookbook 里有一条关键规则:
--之后的参数不会被 bacon 解析,会原样转发给 job 的命令。(Dystroy)
与此同时,Cargo 自己也有 -- 语义:cargo run 的 -- 后面是传给二进制的参数;cargo test 的 -- 后面是传给测试二进制/harness 的参数。(Rust 文档)
因此你会经常看到 双 --:
3.1 传参给二进制(等价 cargo run -- --xx -xx)
bacon run -- -- --xx -xx -xx
- 第一个
--:告诉 bacon “后面都是 job 追加参数” - 第二个
--:告诉 cargo “后面都是二进制参数”(Dystroy)
3.2 同时传给 Cargo 与二进制
例如你要选择 package + 给二进制传参:
bacon run -- -p api -- --port 8080 --log debug
规则仍然是:Cargo 参数放在 Cargo 的 -- 之前,二进制参数放在 Cargo 的 -- 之后。(Rust 文档)
3.3 test 的情况(等价 cargo test -- --nocapture)
bacon test -- -- --nocapture
同样是 “bacon 的 -- + cargo test 的 --”。(Massachusetts Institute of Technology)
4. 单项目(single crate)开发:最小可用到工程化配置
4.1 “开箱即用”的三件套
很多项目只需要这三条:
bacon check
bacon clippy
bacon test
你可以把它理解成 cargo watch -x check/-x clippy/-x test 的更结构化版本。
4.2 跑程序(run)并支持一次性参数
最典型的是本地调试:
bacon run -- -- --config local.toml --port 8080
无需改 bacon.toml,非常适合临时参数。(Dystroy)
4.3 长运行服务(server)自动重启:把“重启策略”写进 job
对 HTTP server / daemon 这类常驻进程,建议单独建一个 job,并明确“变更后先 kill 再重启”的策略(字段名与策略在官方配置文档里有说明)。(Docs.rs)
示例(放在项目 bacon.toml):
[jobs.server]
command = ["cargo", "run", "--bin", "server"]
need_stdout = true
background = false
on_change_strategy = "kill_then_restart"
如果你的程序需要更温柔的退出(例如希望 SIGINT 而不是强杀),可以继续加 kill = [...] 之类的自定义策略(同样属于 job 字段范畴)。(Docs.rs)
5. workspace / monorepo:单一 crate watch、多 crates watch 与折中方案
workspace 的难点不在“能不能跑”,而在“怎样避免无关 crate 的改动导致频繁触发”。
下面给三种常用模式,从“最推荐”到“最省心”。
5.1 推荐:根目录一个 bacon.toml,定义全局与常用 crate jobs
default_job = "check-all"
[jobs.check-all]
command = ["cargo", "check", "--workspace", "--all-targets"]
[jobs.test-all]
command = ["cargo", "test", "--workspace"]
need_stdout = true
[jobs.check-core]
command = ["cargo", "check", "-p", "core"]
default_watch = false
watch = ["crates/core/src", "crates/core/Cargo.toml"]
[jobs.check-api]
command = ["cargo", "check", "-p", "api"]
default_watch = false
watch = ["crates/api/src", "crates/api/Cargo.toml"]
关键点是:对“单 crate job”关闭默认 watch,然后只 watch 该 crate 的目录/清单文件,从源头降低噪音。相关字段(default_watch, watch 等)在配置文档中有完整定义。(Docs.rs)
5.2 更像“在 crate 目录里开发”:使用 workdir
如果你希望命令以某个 crate 目录为工作目录运行(例如相对路径更自然):
[jobs.core]
workdir = "crates/core"
command = ["cargo", "check"]
workdir 也是官方 job 字段的一部分。(Docs.rs)
5.3 同时 watch 多个 crate:多开 bacon 实例(KISS)
bacon 的 UI 通常聚焦一个 active job。要并行观察多条流水线的输出,最简单可靠的方法是:
- 开两个 terminal tab
- 每个 tab 跑一个
bacon <job>(例如bacon check-core/bacon check-api) - 或用 tmux 分屏
这往往比把“多任务并行”塞进单个 job 更稳定、更符合可维护性。
6. 精准 watch:特定文件、gitignore、ignore 组合与常见坑
6.1 只 watch 指定路径(强控触发范围)
[jobs.fast-check]
command = ["cargo", "check", "-p", "core"]
default_watch = false
watch = ["crates/core/src", "crates/core/Cargo.toml"]
6.2 想 watch .env 这类通常被 gitignore 的文件?
bacon 支持根据 gitignore 规则决定是否触发(apply_gitignore),这会导致“改了 .env 但没触发”的错觉。(Docs.rs)
解决办法通常是对该 job 关闭 gitignore 应用:
[jobs.api-server]
command = ["cargo", "run", "-p", "api", "--bin", "server"]
background = false
on_change_strategy = "kill_then_restart"
default_watch = false
watch = ["crates/api/src", ".env"]
apply_gitignore = false
补充背景:gitignore 的本意就是“告诉工具哪些文件不应被纳入版本控制”,很多工具会默认尊重它。(GitHub Docs)
6.3 忽略生成文件/快照文件/临时文件
用 ignore = [...](glob 列表)把噪音挡在外面:
[jobs.test]
command = ["cargo", "test"]
need_stdout = true
ignore = [
".snap.new",
"crates/**/src/generated/*.rs",
]
7. Hook 与自动化:on_success/on_failure、避免循环、导出诊断
7.1 用 on_success/on_failure 实现 hook
bacon 支持在 job 成功或失败后触发动作(on_success / on_failure)。(Docs.rs)
但要注意:官方明确提醒不要写成互相触发的循环(A 成功触发 B、B 成功触发 A),否则会导致 job 一直跑不停。(Docs.rs)
7.2 locations export:把诊断输出给编辑器/脚本
如果你希望 IDE 或脚本读取诊断位置,可以使用 exports(例如 locations),把错误位置写到一个文件里供外部消费。这属于 bacon 的配置能力范畴。(Docs.rs)
8. 故障排查:三类问题一条路径解决
8.1 文件改了但没触发
按这个顺序查:
- 你是不是
default_watch = false但忘记把路径加进watch?(Docs.rs) - 文件是否被 gitignore 覆盖,而该 job
apply_gitignore = true?(Docs.rs) - 是否被
ignore的 glob 规则过滤?(Docs.rs)
8.2 参数传递不符合预期
记住这条心智模型:
所以 run/test 常见就是双 --。
8.3 server 重启不干净
优先确认 job 里用了 on_change_strategy = "kill_then_restart",并按需要自定义 kill 行为。相关字段都在官方配置文档中。(Docs.rs)
9. 一份可以直接落地的 workspace 模板
把下面这个 bacon.toml 放到 workspace 根目录,先跑起来,再按你们真实目录结构微调:
default_job = "check-all"
[jobs.check-all]
command = ["cargo", "check", "--workspace", "--all-targets"]
[jobs.test-all]
command = ["cargo", "test", "--workspace"]
need_stdout = true
[jobs.check-core]
command = ["cargo", "check", "-p", "core"]
default_watch = false
watch = ["crates/core/src", "crates/core/Cargo.toml"]
[jobs.api-server]
command = ["cargo", "run", "-p", "api", "--bin", "server"]
need_stdout = true
background = false
on_change_strategy = "kill_then_restart"
default_watch = false
watch = ["crates/api/src", "crates/api/Cargo.toml", ".env"]
apply_gitignore = false
运行示例:
# 全 workspace check
bacon check-all
# 只盯 core crate
bacon check-core
# 启动 server 并给二进制传参(临时一次)
bacon api-server -- -- --port 8080 --log debug
参考资料
- bacon 配置加载顺序与配置入口(官方站点)(Dystroy)
- bacon cookbook:
--后参数转发与“可能需要双 --”(Dystroy) - Cargo Book:
cargo run参数分隔规则(Rust 文档) - Cargo 测试参数分隔(
cargo test -- ...)说明(Massachusetts Institute of Technology) - bacon GitHub README(jobs 示例与工程化建议)(GitHub)