利用 Cargo build-scripts 将变量在编译期传递给运行时
背景
ESP32 的 Rust 教程示例项目中,第一个项目 hardware-check 演示了如何利用开发板自带的 WIFI 连接无线网。像 WIFI SSID 与密码 这样的变量当然不应该直接写进代码,示例中利用了 toml-cfg#toml_config 宏,读取 cfg.toml 中的值并注入代码(cfg.toml git ignore),以做到在运行时使用这些变量。
这里示例代码稍有误导:在 build.rs 中声明 struct 并调用了一次
#[toml_cfg::toml_config],随即对获取到的值做了一些简单检查。然后在 main.rs 中用完全一样的方法再次声明 struct 并调用宏,并在后续连接 WIFI 时使用。在 main.rs 中还加了一段注释:/// This configuration is picked up at compile time by build.rs from the file cfg.toml。这似乎意味着在这两处相同的 struct 声明及宏调用处存在着某种魔法,使得编译期读取到的配置文件内容可以被传递到运行期使用。但这里并不存在 build.rs 把值 “传给” main.rs 的魔法。toml_cfg::toml_config 是过程宏,它在编译期读取配置并生成 CONFIG 常量;示例里 build.rs 只是重复生成一份用于构建时校验,运行时真正使用的是应用 crate 中那份 CONFIG
我一不习惯 TOML,二不想为此多引一个包,于是想试试能否利用更简单直接的办法实现。
Cargo build-scripts
在 The Cargo book 3.8 Build Scripts 中介绍了构建脚本的一些常见用法与场景,其中就包含了 “使用 rustc-env 指令将指定的环境变量在编译时注入到编译后的 crate,并利用 env! 宏获取这些变量”。
那么类似的,我自然可以将所需的配置项,例如 WIFI 密码写入 .env (当然,git ignore)并在 build.rs 中读取解析,并 println!("cargo:rustc-env={}={}", key, value);
而在程序代码中,使用 let env_foo = env!("FOO").to_string() 将其展开。如此,配置文件中的值在编译期就会确定并注入到代码成为常量。
当然要注意,这里的 “环境变量” 始终仅发生在编译期。一旦编译结束,所有的值都已确定,被 env! 展开的值成为字符串字面量,连同代码一起被编译成二进制。
以上。