crate_git_revision/lib.rs
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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
//! 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, path::Path, 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(), &std::env::current_dir().unwrap());
}
fn __init(w: &mut impl std::io::Write, current_dir: &Path) -> std::io::Result<()> {
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(current_dir.join(".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() {
match Command::new("git")
.current_dir(current_dir)
.arg("rev-parse")
.arg("--git-dir")
.output()
.map(|o| o.stdout)
{
Err(e) => {
writeln!(
w,
"cargo:warning=Error getting git directory to get git revision: {e:?}"
)?;
}
Ok(git_dir) => {
let git_dir = String::from_utf8_lossy(&git_dir);
let git_dir = git_dir.trim();
// Require the build script to rerun if relavent git state changes which
// changes the current git commit.
// - .git/index: Changes if the index/staged files changes, which will
// cause the repo to be dirty.
// - .git/HEAD: Changes if the ref currently in the working directory,
// and potentially the commit, to change.
// - .git/refs: Changes to any files in refs could cause the current
// commit to have changed if the ref in .git/HEAD is changed.
// Note: That changes in the above files may not result in material
// changes to the crate, but changes in any should invalidate the
// revision since the revision can be changed by any of the above.
writeln!(w, "cargo:rerun-if-changed={git_dir}/index")?;
writeln!(w, "cargo:rerun-if-changed={git_dir}/HEAD")?;
writeln!(w, "cargo:rerun-if-changed={git_dir}/refs")?;
match Command::new("git")
.current_dir(current_dir)
.arg("describe")
.arg("--always")
.arg("--exclude='*'")
.arg("--long")
.arg("--abbrev=1000")
.arg("--dirty")
.output()
.map(|o| o.stdout)
{
Err(e) => {
writeln!(
w,
"cargo:warning=Error getting git revision from {current_dir:?}: {e:?}"
)?;
}
Ok(git_describe) => {
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;