Rust 在可执行文件中嵌入代码版本信息

发布时间 2023-11-06 14:16:32作者: 明天有风吹

缘起

我想要最终编译出的可执行文件中包含代码仓库的版本信息

fn main() {
    println!("Hello RustHub");

    // git rev-parse --short HEAD
	let commit_hash = "6c1b45f";
    println!("commit_hash: {}", commit_hash);
}

为了自动获取这个 "6c1b45f" 很自然的我们可以用 Rust 的过程宏(proc-macro), 只需要有一个 cmd_execute! 宏, 并且它可以这样用

let commit_hash = cmd_execute!("git rev-parse --short HEAD");

话不多说, 我们开整

新建一个 crate

# 我们把它命名为 my-proc-macro
cargo new --lib my-proc-macro
cd my-proc-macro
# 添加依赖
cargo add quote
cargo add syn --features full

然后修改 Cargo.toml, 把它改成 proc-macro 类型

[lib]
proc-macro = true

修改 src/lib.rs

use proc_macro::TokenStream;

#[proc_macro]
pub fn cmd_execute(input: TokenStream) -> TokenStream {
    // 只接受一个字符串参数
    let input: syn::LitStr = syn::parse(input).unwrap();

    #[cfg(target_os="windows")]
    let sh = "cmd";
    #[cfg(not(target_os="windows"))]
    let sh = "bash";

    let mut cmd = std::process::Command::new(sh);

    #[cfg(target_os="windows")]
    cmd.arg("/c");
    #[cfg(not(target_os="windows"))]
    cmd.arg("-c");

    cmd.arg(input.value());
    let output = match cmd.output() {
        Ok(out) => out,
        Err(e) => panic!("{}", e),
    };
    // println!("output: {:?}", output);
    if !output.status.success() {
        panic!("The command's output is: {:?}", output);
    }

    let stdout = output.stdout;

    quote::quote! {
        &[
            #(#stdout,)*
        ]
    }.into()
}

注意, 这里的 cmd_execute! 返回值是 &[u8], 如果你需要 String 只需自行转换一下

let commit_hash = cmd_execute!("git rev-parse --short HEAD");
let s = String::from_utf8_lossy(commit_hash);
let s = s.trim_end(); // 去掉末尾的换行符等空白字符

完事

这个短短的宏我已经上传到 crates.io 了, 你可以直接 cargo add cmd-proc-macro 来使用这个 cmd_execute!