Rust in Everything
基于 tauri 2.12.5

每次 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 file capabilities is 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 再编。

💬 关联讨论

发起讨论
加载中...