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