Rust in Everything
基于 tauri 2.12.5

generate_context! 读的不是你指定的配置文件

主线位置:启动主线第一步。generate_context!("...") 的参数决定编译期读哪份配置,进而决定窗口、CSP、内嵌资源、ACL(见 generate_context! 的「宏的入口」);应用的 build script 里 tauri_build::build() 另读一次配置(tauri-build)。两处都只用路径里的目录。

flowchart TB
  arg["参数 alt.conf.json"] --> chk{"同目录有 tauri.conf.json"}
  chk -->|没有| err["no file at path ... exists"]
  chk -->|有| dir["取 parent 目录"]
  dir --> base["读 目录/tauri.conf.json"]
  base --> plat["合并 tauri.macos.conf.json"]
  plat --> envm["合并 TAURI_CONFIG"]
  envm --> cfg["Config"]

本页实验 crate 是 pit1-conf:由模板 base 复制,crate 根目录的 tauri.conf.json 把 productName 改成 from-tauri-conf;另一份 alt.conf.json 的 productName 是 from-alt-conf、窗口标题是 alt。main 只构造 Context 并打印这两项:

fn main() {
  let context: tauri::Context = tauri::generate_context!("alt.conf.json");
  let config = context.config();
  println!(
    "productName = {:?}, window title = {:?}",
    config.product_name,
    config.app.windows.first().map(|w| w.title.as_str())
  );
}

M2 Max、macOS 27、rustc 1.99.0,tauri 2.12.5、tauri-build 2.7.4,--features tauri/custom-protocol。

现象

一:两份配置放在一起

productName = Some("from-tauri-conf"), window title = Some("base")

编译、运行都没有任何提示,拿到的是 tauri.conf.json 的内容。

二:另一份配置单独放进一个目录

把 alt.conf.json 挪到 conf/,宏参数改成 "conf/alt.conf.json",conf/ 里没有别的文件:

error: unexpected end of input, no file at path ~/exp-tauri/pit1-conf/conf/alt.conf.json exists, expected tauri config file
 --> src/main.rs:2:33
  |
2 |   let context: tauri::Context = tauri::generate_context!("conf/alt.conf.json");
  |                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: this error originates in the macro `tauri::generate_context` (in Nightly builds, run with -Z macro-backtrace for more info)

error: could not compile `pit1-conf` (bin "pit1-conf") due to 1 previous error

文件明明在,报错说它不存在。开头的「unexpected end of input, 」是 syn 加的:错误是在参数解析完之后用 input.error(..) 构造的,这时输入已经读到了末尾。

出处

宏解析参数时,ContextItems 的 Parse 实现 把相对路径接到 CARGO_MANIFEST_DIR 上,再用 does_supported_file_name_exist 判断「文件存在」:

pub fn does_supported_file_name_exist(target: Target, path: impl Into<PathBuf>) -> bool {
  let path = path.into();
  let source_file_name = path.file_name().unwrap();
  let lookup_platform_config = ENABLED_FORMATS
    .iter()
    .any(|format| source_file_name == format.into_platform_file_name(target));
  ENABLED_FORMATS.iter().any(|format| {
    path
      .with_file_name(if lookup_platform_config {
        format.into_platform_file_name(target)
      } else {
        format.into_file_name()
      })
      .exists()
  })
}

with_file_name 把文件名换成 tauri.conf.json(或 tauri.conf.json5、Tauri.toml)再看存不存在,传进来的文件名本身从没被检查过。情形二里 conf/tauri.conf.json 不存在,于是报错;情形一里根目录有 tauri.conf.json,于是通过。

接着 get_config 取路径的 parent,交给 read_from:

pub fn read_from(target: Target, root_dir: &Path) -> Result<(Value, Vec<PathBuf>), ConfigError> {
  let (mut config, config_file_path) = parse_value(target, root_dir.join("tauri.conf.json"))?;
  let mut config_paths = vec![config_file_path];
  if let Some((platform_config, path)) = read_platform(target, root_dir)? {
    config_paths.push(path);
    merge(&mut config, &platform_config);
  }
  Ok((config, config_paths))
}

读的是「目录 + tauri.conf.json」,同目录下的平台配置(macOS 上是 tauri.macos.conf.json)自动按 JSON Merge Patch 合并,最后再合并环境变量 TAURI_CONFIG。所以「配置文件」在 tauri 里的单位其实是目录。应用 build script 里的 try_build 也一样:没设 config_path 时用当前目录(crate 根目录),设了就取它的 parent,再调同一个 read_from。

修法

单独的目录,文件叫 tauri.conf.json

把另一份配置放成 conf/tauri.conf.json,宏写 generate_context!("conf/tauri.conf.json")。配置里的相对路径都相对于 conf/,frontendDist 要改成 "../dist"。图标也是:第一次构建失败在这里,

  = help: message: failed to open icon ~/exp-tauri/pit1-conf/conf/icons/icon.png: No such file or directory (os error 2)

再加上 "bundle": { "icon": ["../icons/icon.png"] },构建通过:

== generate_context!("conf/tauri.conf.json")
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s
productName = Some("from-alt-conf"), window title = Some("alt")
== edit conf/tauri.conf.json productName
       Fresh pit1-conf v0.1.0 (~/exp-tauri/pit1-conf)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
productName = Some("from-alt-conf"), window title = Some("alt")

读对了,但第二段暴露了下一个问题:改了 conf/tauri.conf.json 之后 cargo 说 Fresh,程序里还是旧值。宏读配置时不向 cargo 登记任何依赖,监视配置文件的是 build script 打印的 rerun-if-changed,而 build script 读的、监视的都是 crate 根目录的 tauri.conf.json。

让 build script 也用这份配置

fn main() {
  tauri_build::try_build(tauri_build::Attributes::new().config_path("conf/tauri.conf.json"))
    .expect("failed to run tauri-build");
}

再做一遍同样的修改(rustc 命令行截断):

== build.rs: Attributes::new().config_path("conf/tauri.conf.json")
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.84s
productName = Some("from-alt-conf"), window title = Some("alt")
== edit conf/tauri.conf.json productName
       Dirty pit1-conf v0.1.0 (~/exp-tauri/pit1-conf): the file `conf/tauri.conf.json` has changed (1793197716.586713605s, 496552181ns after last build at 1793197716.090161...
   Compiling pit1-conf v0.1.0 (~/exp-tauri/pit1-conf)
     Running `~/exp-tauri/target-pit1/debug/build/pit1-conf-e218091ea073cebd/build-script-build`
     Running `~/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rustc --crate-name pit1_conf`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.02s
productName = Some("from-alt-conf-v2"), window title = Some("alt")

build script 的输出里多了 cargo:rerun-if-changed=conf/tauri.conf.json。这样 tauri-build 用来核对 feature、收集能力的配置,和宏嵌进二进制的配置,终于是同一份。

📝NOTE
冷知识:Attributes::config_path 是 tauri-build 2.7.0 才有的

#15306(提交标题「fix(tauri-build): add Attributes::config_path」)首次发布于 tauri-build 2.7.0,对应 tauri 2.12.0。CHANGELOG 原文:「Added Attributes::config_path to customize config path, deprecated CodegenContext::config_path in favor of this」。在那之前,tauri_build::build() 只会读 crate 目录下的配置;把配置放在别处的项目,build script 和宏读的是两份不同的配置。

只是想改几项:TAURI_CONFIG

如果另一份配置只是在主配置上改几项,不必另起目录:环境变量 TAURI_CONFIG 里放一段 JSON,build script 和宏都会把它合并进去,build script 还打印了 rerun-if-env-changed=TAURI_CONFIG,变量一变就重编:

== TAURI_CONFIG={"productName":"from-env"}
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s
productName = Some("from-env"), window title = Some("alt")

tauri-cli 合并额外配置用的也是这个变量(见 源码地图)。

怎么确认没中招

  • 配置不在 crate 根目录时,检查三件事:文件名是 tauri.conf.json;build.rs 用了 Attributes::config_path 指向同一个文件;配置里的相对路径(frontendDist、bundle.icon、能力文件)都按配置所在目录写。
  • grep rerun-if-changed target/debug/build/<应用 crate>-*/output,里面应该有你实际用的那份配置。
  • 同目录下的 tauri.macos.conf.json 之类会自动合并,不想要就别放在那里。
  • 不确定时在 debug 构建里打印一次 context.config().product_name 或 identifier。

小结

  • generate_context! 和 tauri-build 都只用配置路径的目录,在目录里找 tauri.conf.json(及 .json5、Tauri.toml),再合并平台配置和 TAURI_CONFIG;文件名被忽略。
  • 同目录有 tauri.conf.json 时静默读它;没有时报「no file at path ... exists」,哪怕你给的文件就在那里。
  • 另一份配置要放进单独目录、叫 tauri.conf.json,相对路径按那个目录写,并在 build.rs 里用 Attributes::config_path(tauri-build 2.7.0 起)让构建脚本读同一份、监视同一份。
  • 只改几项时用 TAURI_CONFIG。

💬 关联讨论

发起讨论
加载中...