分类

Cargo 使用 build.rs 动态生成版本号

2019-08-03 10:08 rust

最近有个需求, 要在生成的可执行文件中包含一个版本号字段, 除了像 0.1.0 这样的 在 Cargo.toml 中定义的版本号之外, 最好还包含一下当前的 git commit id.

这个功能可以用 build.rs 脚本来实现. 大致做法也很简单:

  • 在 build.rs 中读取 Cargo.toml 里定义好的版本号, 以及当前的 git 历史, 并写入到编译目录里的一个文本文件
  • src/ 目录的源代码里, 定义一个字符串常量, 它的值在编译期指定的, 就是这个文件文件的内容

编写 build.rs

以下是 build.rs 的内容:

use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::process::Command;

fn get_git_version() -> String {
    let version = env::var("CARGO_PKG_VERSION").unwrap().to_string();

    let child = Command::new("git")
        .args(&["describe", "--always"])
        .output();
    match child {
        Ok(child) => {
            let buf = String::from_utf8(child.stdout).expect("failed to read stdout");
            return version + "-" + &buf;
        },
        Err(err) => {
            eprintln!("`git describe` err: {}", err);
            return version;
        }
    }
}

fn main() {
    let version = get_git_version();
    let mut f = File::create(
        Path::new(&env::var("OUT_DIR").unwrap())
            .join("VERSION")).unwrap();
    f.write_all(version.trim().as_bytes()).unwrap();
}

其中:

  • 环境变量 CARGO_PKG_VERSION 就是 Cargo.toml 中定义好的版本号
  • 调用 git describe --always 命令读取当前的 commit id
  • 将拿到的版本号字符串写入到 $OUT_DIR/VERSION 文件中, $OUT_DIR 这个环境变量在使用 cargo build 命令编译时, 它的值是: /home/shaohua/build-demo/target/debug/build/build-demo-1796000c31d7b82a/out"

在源码中读取版本号

之后我们在 main.rs 中测试一下:

pub const VERSION: &'static str =
    include_str!(concat!(env!("OUT_DIR"), "/VERSION"));

fn main() {
    println!("Hello, world!");
    println!("version: {}", VERSION);
}

这里的 VERSION 常量字符串, 就包含了 $OUT_DIR/VERSION 文件的内容. 读取时用到了几个宏:

  • include_str!() 宏用于读取 UTF-8 编码的文本文件, 默认路径相对于当前源文件
  • concat!() 宏用于合并字符串
  • env!() 宏用于展开编译时的环境变量

来测试一下:

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/build-demo`
Hello, world!
version: 0.1.0-28dfe0f

参考