每次 cargo build 都重编整个应用
主线位置:启动主线之前的编译期。应用 crate 要不要重编,取决于两份清单:build script 打印的
cargo:rerun-if-changed(tauri-build 的「rerun-if-changed 与每次都重编」),和generate_context!用include_bytes!登记进 dep-info 的文件(generate_context! 的「文件名和修改时间」)。前者多登记了一个不存在的路径,就每次都重编;后者只登记宏展开那一刻已有的文件,新文件就进不来。
flowchart TB
cargo["cargo build"] --> rr{"rerun-if-changed 的路径"}
rr -->|"capabilities 不存在"| dirty["Dirty 重跑 build script"]
rr -->|"都没变"| dep{"dep-info 里的文件"}
dirty --> rustc["重编应用 crate"]
dep -->|"有文件变了"| rustc
dep -->|"没变"| fresh["Fresh"]
newf["dist 里新加的文件"] -.->|"不在 dep-info"| fresh
本页实验 crate 是 pit1-rebuild:由模板 base 复制、删掉 capabilities/ 目录,main 只构造 Context 并打印内嵌资源的键,不建窗口。cargo build --offline --features tauri/custom-protocol -v,只保留和这个 crate 有关的行(rustc 的长命令行截断成 ...)。M2 Max、macOS 27、rustc 1.99.0,tauri 2.12.5、tauri-build 2.7.4。
现象
一:什么都没改,连编两次
== build 1 (no capabilities/)
Dirty pit1-rebuild v0.1.0 (~/exp-tauri/pit1-rebuild): the file `capabilities` is missing
Compiling pit1-rebuild v0.1.0 (~/exp-tauri/pit1-rebuild)
Running `~/exp-tauri/target-pit1/debug/build/pit1-rebuild-a99bd8a4d5344291/build-script-build`
Running `~/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rustc --crate-name pit1_rebuild ...
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.99s
== build 2 (no capabilities/)
Dirty pit1-rebuild v0.1.0 (~/exp-tauri/pit1-rebuild): the file `capabilities` is missing
Compiling pit1-rebuild v0.1.0 (~/exp-tauri/pit1-rebuild)
Running `~/exp-tauri/target-pit1/debug/build/pit1-rebuild-a99bd8a4d5344291/build-script-build`
Running `~/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rustc --crate-name pit1_rebuild ...
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.98s
每次都是 Dirty,原因写得很清楚:「the file capabilities is missing」。每次都重跑 build script(tauri_build::build() 重新解析配置、收集权限、写 ACL 文件),再重编应用 crate(generate_context! 重新展开、重新链接)。这个实验 crate 几乎没有代码,一次是 1 秒;真实应用的 crate 越大,这一秒越长,cargo run、cargo test 都要付,cargo check 也一样是 Dirty。
二:往 dist 里加了文件,二进制里没有
先建好 capabilities/,排除上一个问题,再往 frontendDist 指向的 dist/ 里放一个 new.txt:
== add dist/new.txt
Fresh pit1-rebuild v0.1.0 (~/exp-tauri/pit1-rebuild)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
embedded assets: ["/index.html"]
== touch dist/index.html
Dirty pit1-rebuild v0.1.0 (~/exp-tauri/pit1-rebuild): the file `dist/index.html` has changed (1793197612.595237000s, 10s afte
Compiling pit1-rebuild v0.1.0 (~/exp-tauri/pit1-rebuild)
Running `~/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rustc --crate-name pit1_rebuild ...
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.35s
embedded assets: ["/index.html", "/new.txt"]
加文件之后 cargo 认为一切都没变(Fresh),程序里还是只有 /index.html,页面去取 new.txt 会拿不到。touch 一个已有的资源之后才重编,/new.txt 出现。
出处
一:acl::build 在「capabilities/ 存在」或「配置的 app > security > capabilities 为空」时打印 cargo:rerun-if-changed=<crate 目录>/capabilities。第二个条件是有意的:这时目录里的文件就是全部能力的来源,之后新建这个目录并放进能力文件必须触发重跑。代价是目录不存在时,cargo 把这个缺失的路径当作每次都变了。上面的「the file capabilities is missing」就是 cargo 对它的说法。条件的来历(tauri-build 2.7.4 的 #16326)见 tauri-build 那一节的冷知识,这里不重复。
二:generate_context! 把 frontendDist 的每个文件压缩后写进 OUT_DIR 再 include_bytes!,同时对原文件也 include_bytes! 一次,让它们进入 dep-info。cargo 判断要不要重编,看的是 dep-info 里列出的文件,目录本身不在里面,所以「目录里多了一个文件」不会让任何东西变脏。删掉一个已嵌入的文件则会触发重编(实测 cargo 报「the file dist/new.txt is missing」),因为 dep-info 里的那个路径不见了。
两件事方向相反,原因相同:cargo 只认登记过的路径,不认目录的内容。
修法
一:建一个空的 capabilities/ 目录。再编两次:
== build 1 (empty capabilities/)
Dirty pit1-rebuild v0.1.0 (~/exp-tauri/pit1-rebuild): the file `capabilities` has changed (1793197601.779264574s, 687824154ns after last build at 1793197601.09144042...
Compiling pit1-rebuild v0.1.0 (~/exp-tauri/pit1-rebuild)
Running `~/exp-tauri/target-pit1/debug/build/pit1-rebuild-a99bd8a4d5344291/build-script-build`
Running `~/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rustc --crate-name pit1_rebuild ...
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.97s
== build 2 (empty capabilities/)
Fresh pit1-rebuild v0.1.0 (~/exp-tauri/pit1-rebuild)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s
建目录那一次重编是应该的(路径从「不存在」变成了「存在」),之后就是 Fresh。git 不记录空目录,所以要在里面放一个文件提交上去,比如 .gitkeep,否则新克隆的仓库和 CI 上又回到每次重编。更常见的做法是放一个真正的能力文件:CLI 生成的项目自带 capabilities/default.json,碰不到这个问题。
二:往 frontendDist 里加文件之后,touch 一个已经嵌入的文件(比如 index.html),或者 cargo clean -p <应用 crate>。前端构建工具每次构建通常都会重写 index.html,走构建工具的流程一般碰不到;手工往 dist 里放文件时要记得。
怎么确认没中招
- 什么都不改,连跑两次
cargo build -v,第二次应用 crate 那一行应该是Fresh。是Dirty的话,后面就是原因:「the filecapabilitiesis missing」就是本页的第一种情况。 cat target/debug/build/<应用 crate>-*/output | grep rerun-if-changed,列出的每个路径都应该存在。- 新加的前端资源上线前,确认二进制里有它:像
pit1-rebuild这样遍历context.assets().iter(),或者在页面里fetch一次。
小结
- 没有
capabilities/目录、配置里也没列能力时,tauri-build 监视一个不存在的路径,cargo 每次都判它变了:重跑 build script、重编应用 crate。 - 建一个目录并提交其中的文件(
.gitkeep或能力文件)就好。 - 反方向:
frontendDist里新加的文件不在 dep-info 里,cargo build是Fresh,新文件不会被嵌入;touch一个已有资源或cargo clean -p再编。