|
| 1 | +// build.rs |
| 2 | +use std::os::unix::ffi::OsStringExt; |
| 3 | + |
| 4 | +use chrono::{DateTime, Utc}; |
| 5 | + |
| 6 | +/// Returns value of given environment variable or error if missing. |
| 7 | +/// |
| 8 | +/// This also outputs necessary ‘cargo:rerun-if-env-changed’ tag to make sure |
| 9 | +/// build script is rerun if the environment variable changes. |
| 10 | +fn env(key: &str) -> Result<std::ffi::OsString, Box<dyn std::error::Error>> { |
| 11 | + println!("cargo:rerun-if-env-changed={}", key); |
| 12 | + std::env::var_os(key).ok_or_else(|| Box::from(format!("Missing `{}` environmental variable", key))) |
| 13 | +} |
| 14 | + |
| 15 | +/// Calls program with given arguments and returns its standard output. If |
| 16 | +/// calling the program fails or it exits with non-zero exit status returns an |
| 17 | +/// error. |
| 18 | +fn command(prog: &str, args: &[&str], cwd: Option<std::path::PathBuf>) -> Result<Vec<u8>, Box<dyn std::error::Error>> { |
| 19 | + println!("cargo:rerun-if-env-changed=PATH"); |
| 20 | + let mut cmd = std::process::Command::new(prog); |
| 21 | + cmd.args(args); |
| 22 | + cmd.stderr(std::process::Stdio::inherit()); |
| 23 | + if let Some(cwd) = cwd { |
| 24 | + cmd.current_dir(cwd); |
| 25 | + } |
| 26 | + let out = cmd.output()?; |
| 27 | + if out.status.success() { |
| 28 | + let mut stdout = out.stdout; |
| 29 | + if let Some(b'\n') = stdout.last() { |
| 30 | + stdout.pop(); |
| 31 | + if let Some(b'\r') = stdout.last() { |
| 32 | + stdout.pop(); |
| 33 | + } |
| 34 | + } |
| 35 | + Ok(stdout) |
| 36 | + } else if let Some(code) = out.status.code() { |
| 37 | + Err(Box::from(format!("{}: terminated with {}", prog, code))) |
| 38 | + } else { |
| 39 | + Err(Box::from(format!("{}: killed by signal", prog))) |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +/// Checks to see if we're building from a git source and if so attempts to gather information about the git status |
| 44 | +fn get_git_info() -> Result<(), Box<dyn std::error::Error>> { |
| 45 | + let pkg_dir = std::path::PathBuf::from(env("CARGO_MANIFEST_DIR")?); |
| 46 | + let git_dir = command("git", &["rev-parse", "--git-dir"], Some(pkg_dir.clone())); |
| 47 | + let git_dir = match git_dir { |
| 48 | + Ok(git_dir) => { |
| 49 | + println!("cargo:rustc-cfg=BUILDINFO_IS_GIT_BUILD"); |
| 50 | + |
| 51 | + std::path::PathBuf::from(std::ffi::OsString::from_vec(git_dir)) |
| 52 | + } |
| 53 | + Err(msg) => { |
| 54 | + // We're not in a git repo, most likely we're building from a source archive |
| 55 | + println!("cargo:warning=unable to determine git version (not in git repository?)"); |
| 56 | + println!("cargo:warning={}", msg); |
| 57 | + |
| 58 | + // It's unlikely, but possible that someone could run git init. Might as well catch that. |
| 59 | + println!("cargo::rerun-if-changed={}/.git", pkg_dir.display()); |
| 60 | + return Ok(()); |
| 61 | + } |
| 62 | + }; |
| 63 | + |
| 64 | + // Make Cargo rerun us if currently checked out commit or the state of the |
| 65 | + // working tree changes. We try to accomplish that by looking at a few |
| 66 | + // crucial git state files. This probably may result in some false |
| 67 | + // negatives but it’s best we’ve got. |
| 68 | + for subpath in ["HEAD", "logs/HEAD", "index"] { |
| 69 | + let path = git_dir.join(subpath).canonicalize()?; |
| 70 | + println!("cargo:rerun-if-changed={}", path.display()); |
| 71 | + } |
| 72 | + |
| 73 | + // Get the full git hash |
| 74 | + let args = &["rev-parse", "--output-object-format=sha1", "HEAD"]; |
| 75 | + let out = command("git", args, None)?; |
| 76 | + match String::from_utf8_lossy(&out) { |
| 77 | + std::borrow::Cow::Borrowed(full_hash) => { |
| 78 | + println!("cargo:rustc-env=BUILDINFO_GIT_FULL_HASH={}", full_hash.trim()); |
| 79 | + } |
| 80 | + std::borrow::Cow::Owned(full_hash) => return Err(Box::from(format!("git: Invalid output: {}", full_hash))), |
| 81 | + } |
| 82 | + |
| 83 | + // Get the short git hash |
| 84 | + let args = &["rev-parse", "--output-object-format=sha1", "--short", "HEAD"]; |
| 85 | + let out = command("git", args, None)?; |
| 86 | + match String::from_utf8_lossy(&out) { |
| 87 | + std::borrow::Cow::Borrowed(short_hash) => { |
| 88 | + println!("cargo:rustc-env=BUILDINFO_GIT_SHORT_HASH={}", short_hash.trim()); |
| 89 | + } |
| 90 | + std::borrow::Cow::Owned(short_hash) => return Err(Box::from(format!("git: Invalid output: {}", short_hash))), |
| 91 | + } |
| 92 | + |
| 93 | + // Get whether this is built from a dirty tree |
| 94 | + let args = &["status", "--porcelain"]; |
| 95 | + let out = command("git", args, None)?; |
| 96 | + match String::from_utf8_lossy(&out) { |
| 97 | + std::borrow::Cow::Borrowed(output) => match output.trim().len() { |
| 98 | + 0 => {} |
| 99 | + _ => println!("cargo:rustc-cfg=BUILDINFO_IS_DIRTY"), |
| 100 | + }, |
| 101 | + std::borrow::Cow::Owned(output) => return Err(Box::from(format!("git: Invalid output: {}", output))), |
| 102 | + } |
| 103 | + |
| 104 | + // Get the commit summary |
| 105 | + let args = &["show", "--format=\"%s\"", "-s"]; |
| 106 | + let out = command("git", args, None)?; |
| 107 | + match String::from_utf8_lossy(&out) { |
| 108 | + std::borrow::Cow::Borrowed(summary) => { |
| 109 | + println!("cargo:rustc-env=BUILDINFO_GIT_SUMMARY={}", summary.trim()); |
| 110 | + } |
| 111 | + std::borrow::Cow::Owned(summary) => return Err(Box::from(format!("git: Invalid output: {}", summary))), |
| 112 | + } |
| 113 | + |
| 114 | + Ok(()) |
| 115 | +} |
| 116 | + |
| 117 | +fn get_build_time() -> Result<(), Box<dyn std::error::Error>> { |
| 118 | + // Propagate SOURCE_DATE_EPOCH if set |
| 119 | + if let Ok(epoch_env) = env("SOURCE_DATE_EPOCH") { |
| 120 | + if let Ok(seconds) = epoch_env.to_string_lossy().parse::<i64>() { |
| 121 | + if let Some(time) = DateTime::from_timestamp(seconds, 0) { |
| 122 | + println!("cargo:rustc-env=BUILDINFO_BUILD_TIME={}", time.timestamp()); |
| 123 | + return Ok(()); |
| 124 | + } |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + println!("cargo:rustc-env=BUILDINFO_BUILD_TIME={}", Utc::now().timestamp()); |
| 129 | + Ok(()) |
| 130 | +} |
| 131 | + |
| 132 | +fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 133 | + // This should include all top-level directories that contain source code or otherwise modify the build in meaningful ways |
| 134 | + let top_level = std::path::PathBuf::from("../..").canonicalize()?; |
| 135 | + println!("cargo::rerun-if-changed={}/boulder", top_level.display()); |
| 136 | + println!("cargo::rerun-if-changed={}/crates", top_level.display()); |
| 137 | + println!("cargo::rerun-if-changed={}/moss", top_level.display()); |
| 138 | + println!("cargo::rerun-if-changed={}/test", top_level.display()); |
| 139 | + println!("cargo::rerun-if-changed={}/Cargo.toml", top_level.display()); |
| 140 | + |
| 141 | + let version = env("CARGO_PKG_VERSION")?; |
| 142 | + println!("cargo:rustc-env=BUILDINFO_VERSION={}", version.to_string_lossy()); |
| 143 | + |
| 144 | + get_build_time()?; |
| 145 | + |
| 146 | + get_git_info()?; |
| 147 | + |
| 148 | + Ok(()) |
| 149 | +} |
0 commit comments