将git commit hash作为字符串包含在Rust程序中

Vic*_*voy 11 rust

我在git存储库中托管了一个Rust项目,我想让它在某个命令上打印出版本.如何将该版本包含在程序中?我认为构建脚本可以设置环境变量,可以在编译项目本身时使用,但它不起作用:

build.rs:

use std::env;

fn get_git_hash() -> Option<String> {
    use std::process::Command;

    let branch = Command::new("git")
                         .arg("rev-parse")
                         .arg("--abbrev-ref")
                         .arg("HEAD")
                         .output();
    if let Ok(branch_output) = branch {
        let branch_string = String::from_utf8_lossy(&branch_output.stdout);
        let commit = Command::new("git")
                             .arg("rev-parse")
                             .arg("--verify")
                             .arg("HEAD")
                             .output();
        if let Ok(commit_output) = commit {
            let commit_string = String::from_utf8_lossy(&commit_output.stdout);

            return Some(format!("{}, {}",
                        branch_string.lines().next().unwrap_or(""),
                        commit_string.lines().next().unwrap_or("")))
        } else {
            panic!("Can not get git commit: {}", commit_output.unwrap_err());
        }
    } else {
        panic!("Can not get git branch: {}", branch.unwrap_err());
    }
    None
}

fn main() {
    if let Some(git) = get_git_hash() {
        env::set_var("GIT_HASH", git);
    }
}
Run Code Online (Sandbox Code Playgroud)

SRC/main.rs:

pub const GIT_HASH: &'static str = env!("GIT_HASH");

fm main() {
    println!("Git hash: {}", GIT_HASH);
}
Run Code Online (Sandbox Code Playgroud)

错误消息:

error: environment variable `GIT_HASH` not defined
  --> src/main.rs:10:25
   |
10 | pub const GIT_HASH: &'static str = env!("GIT_HASH");
   |   
                                        ^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

有没有办法在编译时传递这样的数据?如果没有环境变量,我如何在构建脚本和源代码之间进行通信?我只能考虑将数据写入某个文件,但我认为这对于这种情况来说太过分了.

ken*_*ytm 19

自从Rust 1.19(货物0.20.0)以来,感谢https://github.com/rust-lang/cargo/pull/3929,您现在可以env!(…)为via rustcrustdocvia 定义编译时环境变量():

println!("cargo:rustc-env=KEY=value");
Run Code Online (Sandbox Code Playgroud)

所以OP的程序可以写成:

// build.rs
use std::process::Command;
fn main() {
    // note: add error checking yourself.
    let output = Command::new("git").args(&["rev-parse", "HEAD"]).output().unwrap();
    let git_hash = String::from_utf8(output.stdout).unwrap();
    println!("cargo:rustc-env=GIT_HASH={}", git_hash);
}
Run Code Online (Sandbox Code Playgroud)
// main.rs
fn main() {
    println!("{}", env!("GIT_HASH"));
    // output something like:
    // 7480b50f3c75eeed88323ec6a718d7baac76290d
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果您仍想支持1.18或更低版本,仍然无法使用此功能.

  • [`vergen 3.1.0`](https://docs.rs/vergen/3.1.0/vergen/fn.generate_cargo_keys.html) 发出 `cargo:rustc-rerun-if-changed=.git/HEAD`。 (3认同)
  • 这里的问题是,如果您使用增量构建,该部分将永远不会被重新编译,从而导致错误结果(在这种情况下具有较旧的 GIT_HASH 值)。Cargo 支持使用 `rerun-if` 指令进行更改检测,但这是先有鸡还是先有蛋的问题:为了确定是否需要重新运行 build.rs,您首先需要运行 build.rs 来获取 git哈希。 (2认同)

ken*_*ytm 9

已经存在vergen一个可以在构建脚本中计算git提交的crate .正如@ DK的答案所描述的那样,构建脚本无法在Rust 1.19之前修改环境变量,因此vergen仍然可以将结果写入OUT_DIR(即vergen仍然不能解决OP的问题,但它应该更容易使用).


用法:

# Cargo.toml
...
[build-dependencies]
vergen = "0.1"
Run Code Online (Sandbox Code Playgroud)
// build.rs
extern crate vergen;
use vergen::*;
fn main() {
    vergen(SHORT_SHA | COMMIT_DATE).unwrap();
}
Run Code Online (Sandbox Code Playgroud)
mod version {
    include!(concat!(env!("OUT_DIR"), "/version.rs"));
}
fn main() {
    println!("commit: {} {}", version::commit_date(), version::short_sha());
    // output something like:
    //        commit: 2017-05-03 a29c7e5
}
Run Code Online (Sandbox Code Playgroud)


Ens*_*lic 8

有一种简单的方法可以做到这一点,无需任何build.rs逻辑或自定义 crate。您只需将当前的 git 哈希作为环境变量直接传递给构建命令,并使用option_env!("PROJECT_VERSION"), 和env!("CARGO_PKG_VERSION")后备在程序中读取它。这些宏在构建期间读取环境变量。

下面的示例构建了这个最小的src/main.rs

fn main() {
    let version = option_env!("PROJECT_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"));
    println!("This binary was built from {}", version);
}
Run Code Online (Sandbox Code Playgroud)

当您构建程序并需要准确的 git 哈希值时(例如在 CI/CD 配置中),您可以在 Cargo 命令前加上PROJECT_VERSION=$(git rev-parse --short HEAD). 像这样cargo run(但也适用于cargo build和其他):

% PROJECT_VERSION=$(git rev-parse --short HEAD) cargo run
This binary was built from 6ca63b2
Run Code Online (Sandbox Code Playgroud)

就我个人而言,我$(git describe)更喜欢$(git rev-parse)前者,因为前者更具描述性(cargo build现在用作示例只是为了变化):

% PROJECT_VERSION=$(git describe) cargo build 
% ./target/debug/your-program
This binary was built from v0.3.0-15-g6ca63b2    # or just 'v0.3.0' if current commit is tagged with that
Run Code Online (Sandbox Code Playgroud)

由于您有CARGO_PKG_VERSION后备方案,您的 IDE 仍然可以为您即时构建文件。同样,对于开发,您可以跳过 pass PROJECT_VERSIONCargo.toml在这种情况下,将使用您的版本:

% cargo run
This binary was built from 0.3.0
Run Code Online (Sandbox Code Playgroud)


DK.*_*DK. 7

我只能考虑将数据写入某个文件,但我认为这对于这种情况来说太过分了.

这是不幸的,因为这做到这一点的唯一方法.环境变量无法工作,因为对环境的更改不会"泄漏"到其他非子进程中.

对于更简单的事情,您可以指示Cargo定义条件编译标志,但这些标志不足以传达字符串[1].

有关从构建脚本生成代码的详细信息,请参阅Cargo文档代码生成部分.


[1]:我的意思是,除非你想将哈希打破160个配置标志,然后在编译的源代码中重新组装它们,但这更具有杀伤力.

  • **进入其他非子进程** 我认为这是在这里实现的关键。构建脚本*在*库编译之前运行,而不是*围绕*它。 (3认同)
  • 当 [cargo#3929](https://github.com/rust-lang/cargo/pull/3929) 合并时,您可以简单地编写 `println!("cargo:rustc-env=GIT_HASH=1fcc849");` (3认同)
  • @Shepmaster:一个标志只给你一点.你有类似`#[cfg(bit_0)] const BIT_0:u8 = 1; #[cfg(not(bit_0))] const BIT_0:u8 = 0;`160次.我*可能*在过去做过类似不可取的事情......\**口哨*\* (2认同)

Son*_*Ex2 6

呃。(我不建议在生产、测试​​、公共代码甚至私有代码中这样做,但我的意思是,它有点起作用?)

const REF: &str = include_str!("../.git/HEAD");
const REF_MASTER: &str = include_str!("../.git/refs/heads/master");

// (elsewhere)
if REF == "ref: refs/heads/master" { REF_MASTER } else { REF }
Run Code Online (Sandbox Code Playgroud)

(除非您正在制作某种代码高尔夫,否则不要使用它。请注意,这是 100% 未经测试的。)