Skip to content

Commit f3c7483

Browse files
committed
Embed Git and Rustc information in binary
1 parent 259487d commit f3c7483

File tree

9 files changed

+314
-66
lines changed

9 files changed

+314
-66
lines changed

.github/workflows/build.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ jobs:
1212

1313
steps:
1414
- uses: actions/checkout@v2
15+
with:
16+
fetch-depth: 0
1517

1618
- uses: actions-rs/toolchain@v1
1719
with:
@@ -49,6 +51,8 @@ jobs:
4951

5052
steps:
5153
- uses: actions/checkout@v2
54+
with:
55+
fetch-depth: 0
5256

5357
- uses: actions-rs/toolchain@v1
5458
with:

.github/workflows/release.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ jobs:
5656

5757
steps:
5858
- uses: actions/checkout@v2
59+
with:
60+
fetch-depth: 0
5961

6062
- uses: actions-rs/toolchain@v1
6163
with:
@@ -82,6 +84,8 @@ jobs:
8284

8385
steps:
8486
- uses: actions/checkout@v2
87+
with:
88+
fetch-depth: 0
8589

8690
- uses: actions-rs/toolchain@v1
8791
with:

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ ansi_term = "0.12.1"
1919
anyhow = "1.0.33"
2020
atty = "0.2.14"
2121
casual = "0.2.0"
22-
clap = { version = "3.0.0-beta.2", features = ["derive"] }
22+
clap = "3.0.0-beta.2"
2323
curl = "0.4.34"
2424
fs2 = "0.4.3"
2525
git2 = "0.13.12"
@@ -41,6 +41,10 @@ url = { version = "2.1.1", features = ["serde"] }
4141
walkdir = "2.3.1"
4242
which = { version = "4.0.2", default-features = false }
4343

44+
[build-dependencies]
45+
anyhow = "1.0.32"
46+
once_cell = "1.4.1"
47+
4448
[dev-dependencies]
4549
pest = "2.1.3"
4650
pest_derive = "2.1.0"

build.rs

Lines changed: 128 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,132 @@
1-
//! Simple build script to pass through the target we are building.
1+
use std::env;
2+
use std::io;
3+
use std::path::PathBuf;
4+
use std::process;
25

3-
fn main() {
6+
use anyhow::{bail, Context, Result};
7+
use once_cell::sync::Lazy;
8+
9+
/// The directory containing the `Cargo.toml` file.
10+
static CARGO_MANIFEST_DIR: Lazy<PathBuf> =
11+
Lazy::new(|| PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()));
12+
13+
/// Nicely format an error message for when the subprocess didn't exit
14+
/// successfully.
15+
pub fn format_error_msg(cmd: &process::Command, output: process::Output) -> String {
16+
let stdout = String::from_utf8_lossy(&output.stdout);
17+
let stderr = String::from_utf8_lossy(&output.stderr);
18+
let mut msg = format!(
19+
"subprocess didn't exit successfully `{:?}` ({})",
20+
cmd, output.status
21+
);
22+
if !stdout.trim().is_empty() {
23+
msg.push_str(&format!("\n--- stdout\n{}", stdout));
24+
}
25+
if !stderr.trim().is_empty() {
26+
msg.push_str(&format!("\n--- stderr\n{}", stderr));
27+
}
28+
msg
29+
}
30+
31+
/// Whether underlying error kind for the given error is
32+
/// `io::ErrorKind::NotFound`.
33+
pub fn is_io_not_found(error: &anyhow::Error) -> bool {
34+
for cause in error.chain() {
35+
if let Some(io_error) = cause.downcast_ref::<io::Error>() {
36+
return io_error.kind() == io::ErrorKind::NotFound;
37+
}
38+
}
39+
false
40+
}
41+
42+
trait CommandExt {
43+
/// Run the command return the standard output as a UTF-8 string.
44+
fn output_text(&mut self) -> Result<String>;
45+
}
46+
47+
impl CommandExt for process::Command {
48+
/// Run the command return the standard output as a UTF-8 string.
49+
fn output_text(&mut self) -> Result<String> {
50+
let output = self
51+
.output()
52+
.with_context(|| format!("could not execute subprocess: `{:?}`", self))?;
53+
if !output.status.success() {
54+
bail!(format_error_msg(self, output));
55+
}
56+
Ok(String::from_utf8(output.stdout).context("failed to parse stdout")?)
57+
}
58+
}
59+
60+
/// Simple macro to run a Git subcommand and set the result as a rustc
61+
/// environment variable.
62+
///
63+
/// Note:
64+
/// - The Cargo manifest directory is passed as the Git directory.
65+
/// - If the Git subcommand is not available then this macro will `return
66+
/// Ok(())`.
67+
macro_rules! print_git_env {
68+
($key:expr, $cmd:expr) => {{
69+
let mut split = $cmd.split_whitespace();
70+
let value = match process::Command::new(split.next().unwrap())
71+
.arg("-C")
72+
.arg(&*CARGO_MANIFEST_DIR)
73+
.args(split)
74+
.output_text()
75+
{
76+
Ok(text) => text.trim().to_string(),
77+
Err(err) if is_io_not_found(&err) => return Ok(()),
78+
Err(err) => return Err(err.into()),
79+
};
80+
println!("cargo:rustc-env={}={}", $key, value);
81+
}};
82+
}
83+
84+
/// Fetch Git info and set as rustc environment variables.
85+
///
86+
/// If the Git subcommand is missing or the `.git` directory does not exist then
87+
/// no errors will be produced.
88+
fn print_git_envs() -> Result<()> {
89+
let git_dir = CARGO_MANIFEST_DIR.join(".git");
90+
println!("cargo:rerun-if-changed={}", git_dir.display());
91+
if !git_dir.exists() {
92+
return Ok(());
93+
}
94+
print_git_env!(
95+
"GIT_COMMIT_DATE",
96+
"git log -1 --no-show-signature --date=short --format=%cd"
97+
);
98+
print_git_env!("GIT_COMMIT_HASH", "git rev-parse HEAD");
99+
print_git_env!("GIT_COMMIT_SHORT_HASH", "git rev-parse --short=9 HEAD");
100+
Ok(())
101+
}
102+
103+
/// Fetch rustc info and set as rustc environment variables.
104+
fn print_rustc_envs() -> Result<()> {
105+
let text = process::Command::new(env::var("RUSTC")?)
106+
.arg("--verbose")
107+
.arg("--version")
108+
.output_text()?;
109+
let mut lines = text.lines();
4110
println!(
5-
"cargo:rustc-env=TARGET={}",
6-
std::env::var("TARGET").unwrap()
111+
"cargo:rustc-env=RUSTC_VERSION_SUMMARY={}",
112+
lines.next().unwrap()
7113
);
114+
for line in lines {
115+
let mut split = line.splitn(2, ": ");
116+
let key = split.next().unwrap();
117+
let value = split.next().unwrap();
118+
println!(
119+
"cargo:rustc-env=RUSTC_VERSION_{}={}",
120+
key.replace('-', "_").replace(' ', "_").to_uppercase(),
121+
value,
122+
)
123+
}
124+
Ok(())
125+
}
126+
127+
fn main() -> Result<()> {
128+
print_git_envs().context("failed to fetch Git information")?;
129+
print_rustc_envs().context("failed to fetch rustc information")?;
130+
println!("cargo:rustc-env=TARGET={}", env::var("TARGET")?);
131+
Ok(())
8132
}

src/build.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//! Build information.
2+
//!
3+
//! Most of this information is generated by Cargo and the build script for this
4+
//! crate.
5+
6+
use once_cell::sync::Lazy;
7+
8+
/// This is the name defined in the Cargo manifest.
9+
pub const CRATE_NAME: &str = env!("CARGO_PKG_NAME");
10+
11+
/// This is the version defined in the Cargo manifest.
12+
pub const CRATE_RELEASE: &str = env!("CARGO_PKG_VERSION");
13+
14+
/// This is the release with extra Git information if available.
15+
pub static CRATE_VERSION: Lazy<String> = Lazy::new(|| {
16+
GIT.as_ref()
17+
.map(|git| {
18+
format!(
19+
"{} ({} {})",
20+
CRATE_RELEASE, git.commit_short_hash, git.commit_date
21+
)
22+
})
23+
.unwrap_or_else(|| CRATE_RELEASE.to_string())
24+
});
25+
26+
/// This is the release with extra Git and Rustc information if available.
27+
pub static CRATE_LONG_VERSION: Lazy<String> =
28+
Lazy::new(|| format!("{}\n{}", &*CRATE_VERSION, env!("RUSTC_VERSION_SUMMARY")));
29+
30+
/// This is a very verbose description of the crate version.
31+
pub static CRATE_VERBOSE_VERSION: Lazy<String> = Lazy::new(|| {
32+
let mut v = CRATE_VERSION.clone();
33+
macro_rules! push {
34+
($($arg:tt)*) => {v.push('\n'); v.push_str(&format!($($arg)+))};
35+
}
36+
if let Some(git) = GIT.as_ref() {
37+
push!("");
38+
push!("Details:");
39+
push!(" binary: {}", CRATE_NAME);
40+
push!(" release: {}", CRATE_RELEASE);
41+
push!(" commit-hash: {}", git.commit_hash);
42+
push!(" commit-date: {}", git.commit_date);
43+
push!(" target: {}", env!("TARGET"));
44+
}
45+
push!("");
46+
push!("Compiled with:");
47+
push!(" binary: {}", env!("RUSTC_VERSION_BINARY"));
48+
push!(" release: {}", env!("RUSTC_VERSION_RELEASE"));
49+
push!(" commit-hash: {}", env!("RUSTC_VERSION_COMMIT_HASH"));
50+
push!(" commit-date: {}", env!("RUSTC_VERSION_COMMIT_DATE"));
51+
push!(" host: {}", env!("RUSTC_VERSION_HOST"));
52+
v
53+
});
54+
55+
struct Git<'a> {
56+
commit_date: &'a str,
57+
commit_hash: &'a str,
58+
commit_short_hash: &'a str,
59+
}
60+
61+
static GIT: Lazy<Option<Git>> = Lazy::new(|| {
62+
match (
63+
option_env!("GIT_COMMIT_DATE"),
64+
option_env!("GIT_COMMIT_HASH"),
65+
option_env!("GIT_COMMIT_SHORT_HASH"),
66+
) {
67+
(Some(commit_date), Some(commit_hash), Some(commit_short_hash)) => Some(Git {
68+
commit_date,
69+
commit_hash,
70+
commit_short_hash,
71+
}),
72+
(None, None, None) => None,
73+
vars => {
74+
panic!("unexpected Git information: {:?}", vars)
75+
}
76+
}
77+
});

0 commit comments

Comments
 (0)