1#![deny(clippy::pedantic)]
23//! Fedimint build scripts
4//!
5//! Implements detection of build hash. Used both internally in all
6//! public-consumed binaries of `fedimint`, and also in custom builds
7//! that can included 3rd party Fedimint modules.
8//!
9//! To use include:
10//!
11//! ```norust
12//! [build-dependencies]
13//! fedimint-build = { version = "=0.4.0-alpha", path = "../fedimint-build" }
14//! ```
15//!
16//! in `Cargo.toml`, and:
17//!
18//! ```ignore
19//! fn main() {
20//! fedimint_build::set_code_version();
21//! }
22//! ```
23//!
24//! in `build.rs` script.
25//!
26//! This will define `FEDIMINT_BUILD_CODE_VERSION` at the build time, which can
27//! be accessed via `fedimint_build_code_version_env!()` and passed to binary
28//! builders like `FedimintCli::new`.
29pub mod envs;
3031use std::env;
32use std::path::Path;
33use std::process::Command;
3435use crate::envs::{FEDIMINT_BUILD_CODE_VERSION_ENV, FORCE_GIT_HASH_ENV};
3637fn set_code_version_inner() -> Result<(), String> {
38println!("cargo:rerun-if-env-changed={FORCE_GIT_HASH_ENV}");
3940if let Ok(hash) = env::var(FORCE_GIT_HASH_ENV) {
41eprintln!("Forced hash via {FORCE_GIT_HASH_ENV} to {hash}");
42println!("cargo:rustc-env={FEDIMINT_BUILD_CODE_VERSION_ENV}={hash}");
43return Ok(());
44 }
4546// In case we are compiling a released crate we don't have a git directory, but
47 // can read the version hash from .cargo_vcs_info.json
48if let Ok(file) = std::fs::File::open("./.cargo_vcs_info.json") {
49let info: serde_json::Value = serde_json::from_reader(file)
50 .map_err(|e| format!("Failed to parse .cargo_vcs_info.json: {e}"))?;
51let hash = info["git"]["sha1"].as_str().ok_or_else(|| {
52format!("Failed to parse .cargo_vcs_info.json: no `.git.sha` field: {info:?}")
53 })?;
54println!("cargo:rustc-env={FEDIMINT_BUILD_CODE_VERSION_ENV}={hash}");
55return Ok(());
56 }
5758// built somewhere in the `$HOME/.cargo/...`, probably detecting it and
59 // using a release version instead.
6061 // Note: best effort approach to force a re-run when the git hash in
62 // the local repo changes without wrecking the incremental compilation
63 // completely.
64for base in [
65// The relative path of git files might vary, so we just try a lot of cases.
66 // If you go deeper than that, you're silly.
67".",
68"..",
69"../..",
70"../../..",
71"../../../..",
72"../../../../..",
73 ] {
74let p = &format!("{base}/.git/HEAD");
75if Path::new(&p).exists() {
76println!("cargo:rerun-if-changed={p}");
77 }
78// Common(?) `git workdir` setup
79let p = &format!("{base}/HEAD");
80if Path::new(&p).exists() {
81println!("cargo:rerun-if-changed={p}");
82 }
83 }
8485let hash = call_cmd("git", &["rev-parse", "HEAD"])?;
8687let dirty = !call_cmd("git", &["status", "--porcelain"])?.is_empty();
8889let hash = if dirty {
90// Since our hash needs to be constant, mark the dirty
91 // state by replacing the middle with 0s. This should
92 // be noticeable enough, while letting find out the
93 // root commit anyway.
94format!("{}00000000{}", &hash[0..16], &hash[(40 - 16)..40])
95 } else {
96 hash
97 };
9899println!("cargo:rustc-env={FEDIMINT_BUILD_CODE_VERSION_ENV}={hash}");
100101Ok(())
102}
103104fn call_cmd(cmd: &str, args: &[&str]) -> Result<String, String> {
105let output = match Command::new(cmd).args(args).output() {
106Ok(output) => output,
107Err(e) => {
108return Err(format!("Failed to execute `git` command: {e}"));
109 }
110 };
111112if !output.status.success() {
113return Err(format!(
114"`git` command failed: stderr: {}; stdout: {}",
115 String::from_utf8_lossy(&output.stderr),
116 String::from_utf8_lossy(&output.stdout)
117 ));
118 }
119120match String::from_utf8(output.stdout) {
121Ok(o) => Ok(o.trim().to_string()),
122Err(e) => Err(format!("Invalid UTF-8 sequence detected: {e}")),
123 }
124}
125126/// Run from a `build.rs` script to detect code version. See [`crate`] for
127/// description.
128pub fn set_code_version() {
129if let Err(e) = set_code_version_inner() {
130eprintln!(
131"Failed to detect git hash version: {e}. Set {FORCE_GIT_HASH_ENV} to enforce the version and skip auto-detection."
132);
133println!(
134"cargo:rustc-env={FEDIMINT_BUILD_CODE_VERSION_ENV}=0000000000000000000000000000000000000000"
135);
136 }
137}