crate_git_revision/lib.rs
1//! Embed the git revision of a crate in its build.
2//!
3//! Supports embedding the version from a local or remote git repository the build
4//! is occurring in, as well as when `cargo install` or depending on a crate
5//! published to crates.io.
6//!
7//! It extracts the git revision in two ways:
8//! - From the `.cargo_vcs_info.json` file embedded in published crates.
9//! - From the git repository the build is occurring from in unpublished crates.
10//!
11//! Injects an environment variable `GIT_REVISION` into the build that contains
12//! the full git revision, with a `-dirty` suffix if the working directory is
13//! dirty.
14//!
15//! Requires the use of a build.rs build script. See [Build Scripts]() for more
16//! details on how Rust build scripts work.
17//!
18//! [Build Scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html
19//!
20//! ### Examples
21//!
22//! Add the following to the crate's `Cargo.toml` file:
23//!
24//! ```toml
25//! [build_dependencies]
26//! crate-git-revision = "0.0.2"
27//! ```
28//!
29//! Add the following to the crate's `build.rs` file:
30//!
31//! ```rust
32//! crate_git_revision::init();
33//! ```
34//!
35//! Add the following to the crate's `lib.rs` or `main.rs` file:
36//!
37//! ```ignore
38//! pub const GIT_REVISION: &str = env!("GIT_REVISION");
39//! ```
40
41use std::{fs::read_to_string, path::Path, process::Command, str};
42
43/// Initialize the GIT_REVISION environment variable with the git revision of
44/// the current crate.
45///
46/// Intended to be called from within a build script, `build.rs` file, for the
47/// crate.
48pub fn init() {
49 let _res = __init(&mut std::io::stdout(), &std::env::current_dir().unwrap());
50}
51
52fn __init(w: &mut impl std::io::Write, current_dir: &Path) -> std::io::Result<()> {
53 let mut git_sha: Option<String> = None;
54
55 // Read the git revision from the JSON file embedded by cargo publish. This
56 // will get the version from published crates.
57 if let Ok(vcs_info) = read_to_string(current_dir.join(".cargo_vcs_info.json")) {
58 let vcs_info: Result<CargoVcsInfo, _> = serde_json::from_str(&vcs_info);
59 if let Ok(vcs_info) = vcs_info {
60 git_sha = Some(vcs_info.git.sha1);
61 }
62 }
63
64 // Read the git revision from the git repository containing the code being
65 // built.
66 if git_sha.is_none() {
67 match Command::new("git")
68 .current_dir(current_dir)
69 .arg("rev-parse")
70 .arg("--git-dir")
71 .output()
72 .map(|o| o.stdout)
73 {
74 Err(e) => {
75 writeln!(
76 w,
77 "cargo:warning=Error getting git directory to get git revision: {e:?}"
78 )?;
79 }
80 Ok(git_dir) => {
81 let git_dir = String::from_utf8_lossy(&git_dir);
82 let git_dir = git_dir.trim();
83
84 // Require the build script to rerun if relavent git state changes which
85 // changes the current git commit.
86 // - .git/index: Changes if the index/staged files changes, which will
87 // cause the repo to be dirty.
88 // - .git/HEAD: Changes if the ref currently in the working directory,
89 // and potentially the commit, to change.
90 // - .git/refs: Changes to any files in refs could cause the current
91 // commit to have changed if the ref in .git/HEAD is changed.
92 // Note: That changes in the above files may not result in material
93 // changes to the crate, but changes in any should invalidate the
94 // revision since the revision can be changed by any of the above.
95 writeln!(w, "cargo:rerun-if-changed={git_dir}/index")?;
96 writeln!(w, "cargo:rerun-if-changed={git_dir}/HEAD")?;
97 writeln!(w, "cargo:rerun-if-changed={git_dir}/refs")?;
98
99 match Command::new("git")
100 .current_dir(current_dir)
101 .arg("describe")
102 .arg("--always")
103 .arg("--exclude='*'")
104 .arg("--long")
105 .arg("--abbrev=1000")
106 .arg("--dirty")
107 .output()
108 .map(|o| o.stdout)
109 {
110 Err(e) => {
111 writeln!(
112 w,
113 "cargo:warning=Error getting git revision from {current_dir:?}: {e:?}"
114 )?;
115 }
116 Ok(git_describe) => {
117 git_sha = str::from_utf8(&git_describe).ok().map(str::to_string);
118 }
119 }
120 }
121 }
122 }
123
124 if let Some(git_sha) = git_sha {
125 writeln!(w, "cargo:rustc-env=GIT_REVISION={git_sha}")?;
126 }
127
128 Ok(())
129}
130
131#[derive(serde_derive::Serialize, serde_derive::Deserialize, Default)]
132struct CargoVcsInfo {
133 git: CargoVcsInfoGit,
134}
135
136#[derive(serde_derive::Serialize, serde_derive::Deserialize, Default)]
137struct CargoVcsInfoGit {
138 sha1: String,
139}
140
141mod test;