1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! Embed the git revision of a crate in its build.
//!
//! Supports embedding the version from a local or remote git repository the build
//! is occurring in, as well as when `cargo install` or depending on a crate
//! published to crates.io.
//!
//! It extracts the git revision in two ways:
//! - From the `.cargo_vcs_info.json` file embedded in published crates.
//! - From the git repository the build is occurring from in unpublished crates.
//!
//! Injects an environment variable `GIT_REVISION` into the build that contains
//! the full git revision, with a `-dirty` suffix if the working directory is
//! dirty.
//!
//! Requires the use of a build.rs build script. See [Build Scripts]() for more
//! details on how Rust build scripts work.
//!
//! [Build Scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html
//!
//! ### Examples
//!
//! Add the following to the crate's `Cargo.toml` file:
//!
//! ```toml
//! [build_dependencies]
//! crate-git-revision = "0.0.2"
//! ```
//!
//! Add the following to the crate's `build.rs` file:
//!
//! ```rust
//! crate_git_revision::init();
//! ```
//!
//! Add the following to the crate's `lib.rs` or `main.rs` file:
//!
//! ```ignore
//! pub const GIT_REVISION: &str = env!("GIT_REVISION");
//! ```

use std::{fs::read_to_string, process::Command, str};

/// Initialize the GIT_REVISION environment variable with the git revision of
/// the current crate.
///
/// Intended to be called from within a build script, `build.rs` file, for the
/// crate.
pub fn init() {
    let _res = __init(&mut std::io::stdout());
}

fn __init(w: &mut impl std::io::Write) -> std::io::Result<()> {
    // Require the build script to rerun if anything in the directory changes,
    // since anything changing could affect the git revision.
    writeln!(w, "cargo:rerun-if-changed=.")?;

    let mut git_sha: Option<String> = None;

    // Read the git revision from the JSON file embedded by cargo publish. This
    // will get the version from published crates.
    if let Ok(vcs_info) = read_to_string(".cargo_vcs_info.json") {
        let vcs_info: Result<CargoVcsInfo, _> = serde_json::from_str(&vcs_info);
        if let Ok(vcs_info) = vcs_info {
            git_sha = Some(vcs_info.git.sha1);
        }
    }

    // Read the git revision from the git repository containing the code being
    // built.
    if git_sha.is_none() {
        if let Ok(git_describe) = Command::new("git")
            .arg("describe")
            .arg("--always")
            .arg("--exclude='*'")
            .arg("--long")
            .arg("--abbrev=1000")
            .arg("--dirty")
            .output()
            .map(|o| o.stdout)
        {
            git_sha = str::from_utf8(&git_describe).ok().map(str::to_string);
        }
    }

    if let Some(git_sha) = git_sha {
        writeln!(w, "cargo:rustc-env=GIT_REVISION={git_sha}")?;
    }

    Ok(())
}

#[derive(serde_derive::Serialize, serde_derive::Deserialize, Default)]
struct CargoVcsInfo {
    git: CargoVcsInfoGit,
}

#[derive(serde_derive::Serialize, serde_derive::Deserialize, Default)]
struct CargoVcsInfoGit {
    sha1: String,
}

mod test;