Skip to content

Commit

Permalink
Embed Git and Rustc information in binary
Browse files Browse the repository at this point in the history
  • Loading branch information
rossmacarthur committed Nov 21, 2020
1 parent 259487d commit f3c7483
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 66 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/build.yaml
Expand Up @@ -12,6 +12,8 @@ jobs:

steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0

- uses: actions-rs/toolchain@v1
with:
Expand Down Expand Up @@ -49,6 +51,8 @@ jobs:

steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0

- uses: actions-rs/toolchain@v1
with:
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yaml
Expand Up @@ -56,6 +56,8 @@ jobs:

steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0

- uses: actions-rs/toolchain@v1
with:
Expand All @@ -82,6 +84,8 @@ jobs:

steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0

- uses: actions-rs/toolchain@v1
with:
Expand Down
6 changes: 5 additions & 1 deletion Cargo.toml
Expand Up @@ -19,7 +19,7 @@ ansi_term = "0.12.1"
anyhow = "1.0.33"
atty = "0.2.14"
casual = "0.2.0"
clap = { version = "3.0.0-beta.2", features = ["derive"] }
clap = "3.0.0-beta.2"
curl = "0.4.34"
fs2 = "0.4.3"
git2 = "0.13.12"
Expand All @@ -41,6 +41,10 @@ url = { version = "2.1.1", features = ["serde"] }
walkdir = "2.3.1"
which = { version = "4.0.2", default-features = false }

[build-dependencies]
anyhow = "1.0.32"
once_cell = "1.4.1"

[dev-dependencies]
pest = "2.1.3"
pest_derive = "2.1.0"
Expand Down
132 changes: 128 additions & 4 deletions build.rs
@@ -1,8 +1,132 @@
//! Simple build script to pass through the target we are building.
use std::env;
use std::io;
use std::path::PathBuf;
use std::process;

fn main() {
use anyhow::{bail, Context, Result};
use once_cell::sync::Lazy;

/// The directory containing the `Cargo.toml` file.
static CARGO_MANIFEST_DIR: Lazy<PathBuf> =
Lazy::new(|| PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()));

/// Nicely format an error message for when the subprocess didn't exit
/// successfully.
pub fn format_error_msg(cmd: &process::Command, output: process::Output) -> String {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let mut msg = format!(
"subprocess didn't exit successfully `{:?}` ({})",
cmd, output.status
);
if !stdout.trim().is_empty() {
msg.push_str(&format!("\n--- stdout\n{}", stdout));
}
if !stderr.trim().is_empty() {
msg.push_str(&format!("\n--- stderr\n{}", stderr));
}
msg
}

/// Whether underlying error kind for the given error is
/// `io::ErrorKind::NotFound`.
pub fn is_io_not_found(error: &anyhow::Error) -> bool {
for cause in error.chain() {
if let Some(io_error) = cause.downcast_ref::<io::Error>() {
return io_error.kind() == io::ErrorKind::NotFound;
}
}
false
}

trait CommandExt {
/// Run the command return the standard output as a UTF-8 string.
fn output_text(&mut self) -> Result<String>;
}

impl CommandExt for process::Command {
/// Run the command return the standard output as a UTF-8 string.
fn output_text(&mut self) -> Result<String> {
let output = self
.output()
.with_context(|| format!("could not execute subprocess: `{:?}`", self))?;
if !output.status.success() {
bail!(format_error_msg(self, output));
}
Ok(String::from_utf8(output.stdout).context("failed to parse stdout")?)
}
}

/// Simple macro to run a Git subcommand and set the result as a rustc
/// environment variable.
///
/// Note:
/// - The Cargo manifest directory is passed as the Git directory.
/// - If the Git subcommand is not available then this macro will `return
/// Ok(())`.
macro_rules! print_git_env {
($key:expr, $cmd:expr) => {{
let mut split = $cmd.split_whitespace();
let value = match process::Command::new(split.next().unwrap())
.arg("-C")
.arg(&*CARGO_MANIFEST_DIR)
.args(split)
.output_text()
{
Ok(text) => text.trim().to_string(),
Err(err) if is_io_not_found(&err) => return Ok(()),
Err(err) => return Err(err.into()),
};
println!("cargo:rustc-env={}={}", $key, value);
}};
}

/// Fetch Git info and set as rustc environment variables.
///
/// If the Git subcommand is missing or the `.git` directory does not exist then
/// no errors will be produced.
fn print_git_envs() -> Result<()> {
let git_dir = CARGO_MANIFEST_DIR.join(".git");
println!("cargo:rerun-if-changed={}", git_dir.display());
if !git_dir.exists() {
return Ok(());
}
print_git_env!(
"GIT_COMMIT_DATE",
"git log -1 --no-show-signature --date=short --format=%cd"
);
print_git_env!("GIT_COMMIT_HASH", "git rev-parse HEAD");
print_git_env!("GIT_COMMIT_SHORT_HASH", "git rev-parse --short=9 HEAD");
Ok(())
}

/// Fetch rustc info and set as rustc environment variables.
fn print_rustc_envs() -> Result<()> {
let text = process::Command::new(env::var("RUSTC")?)
.arg("--verbose")
.arg("--version")
.output_text()?;
let mut lines = text.lines();
println!(
"cargo:rustc-env=TARGET={}",
std::env::var("TARGET").unwrap()
"cargo:rustc-env=RUSTC_VERSION_SUMMARY={}",
lines.next().unwrap()
);
for line in lines {
let mut split = line.splitn(2, ": ");
let key = split.next().unwrap();
let value = split.next().unwrap();
println!(
"cargo:rustc-env=RUSTC_VERSION_{}={}",
key.replace('-', "_").replace(' ', "_").to_uppercase(),
value,
)
}
Ok(())
}

fn main() -> Result<()> {
print_git_envs().context("failed to fetch Git information")?;
print_rustc_envs().context("failed to fetch rustc information")?;
println!("cargo:rustc-env=TARGET={}", env::var("TARGET")?);
Ok(())
}
77 changes: 77 additions & 0 deletions src/build.rs
@@ -0,0 +1,77 @@
//! Build information.
//!
//! Most of this information is generated by Cargo and the build script for this
//! crate.

use once_cell::sync::Lazy;

/// This is the name defined in the Cargo manifest.
pub const CRATE_NAME: &str = env!("CARGO_PKG_NAME");

/// This is the version defined in the Cargo manifest.
pub const CRATE_RELEASE: &str = env!("CARGO_PKG_VERSION");

/// This is the release with extra Git information if available.
pub static CRATE_VERSION: Lazy<String> = Lazy::new(|| {
GIT.as_ref()
.map(|git| {
format!(
"{} ({} {})",
CRATE_RELEASE, git.commit_short_hash, git.commit_date
)
})
.unwrap_or_else(|| CRATE_RELEASE.to_string())
});

/// This is the release with extra Git and Rustc information if available.
pub static CRATE_LONG_VERSION: Lazy<String> =
Lazy::new(|| format!("{}\n{}", &*CRATE_VERSION, env!("RUSTC_VERSION_SUMMARY")));

/// This is a very verbose description of the crate version.
pub static CRATE_VERBOSE_VERSION: Lazy<String> = Lazy::new(|| {
let mut v = CRATE_VERSION.clone();
macro_rules! push {
($($arg:tt)*) => {v.push('\n'); v.push_str(&format!($($arg)+))};
}
if let Some(git) = GIT.as_ref() {
push!("");
push!("Details:");
push!(" binary: {}", CRATE_NAME);
push!(" release: {}", CRATE_RELEASE);
push!(" commit-hash: {}", git.commit_hash);
push!(" commit-date: {}", git.commit_date);
push!(" target: {}", env!("TARGET"));
}
push!("");
push!("Compiled with:");
push!(" binary: {}", env!("RUSTC_VERSION_BINARY"));
push!(" release: {}", env!("RUSTC_VERSION_RELEASE"));
push!(" commit-hash: {}", env!("RUSTC_VERSION_COMMIT_HASH"));
push!(" commit-date: {}", env!("RUSTC_VERSION_COMMIT_DATE"));
push!(" host: {}", env!("RUSTC_VERSION_HOST"));
v
});

struct Git<'a> {
commit_date: &'a str,
commit_hash: &'a str,
commit_short_hash: &'a str,
}

static GIT: Lazy<Option<Git>> = Lazy::new(|| {
match (
option_env!("GIT_COMMIT_DATE"),
option_env!("GIT_COMMIT_HASH"),
option_env!("GIT_COMMIT_SHORT_HASH"),
) {
(Some(commit_date), Some(commit_hash), Some(commit_short_hash)) => Some(Git {
commit_date,
commit_hash,
commit_short_hash,
}),
(None, None, None) => None,
vars => {
panic!("unexpected Git information: {:?}", vars)
}
}
});

0 comments on commit f3c7483

Please sign in to comment.