1#![deny(clippy::pedantic)]
2
3pub mod envs;
30
31use std::env;
32use std::path::Path;
33use std::process::Command;
34
35use crate::envs::{FEDIMINT_BUILD_CODE_VERSION_ENV, FORCE_GIT_HASH_ENV};
36
37fn set_code_version_inner() -> Result<(), String> {
38    println!("cargo:rerun-if-env-changed={FORCE_GIT_HASH_ENV}");
39
40    if let Ok(hash) = env::var(FORCE_GIT_HASH_ENV) {
41        eprintln!("Forced hash via {FORCE_GIT_HASH_ENV} to {hash}");
42        println!("cargo:rustc-env={FEDIMINT_BUILD_CODE_VERSION_ENV}={hash}");
43        return Ok(());
44    }
45
46    if let Ok(file) = std::fs::File::open("./.cargo_vcs_info.json") {
49        let info: serde_json::Value = serde_json::from_reader(file)
50            .map_err(|e| format!("Failed to parse .cargo_vcs_info.json: {e}"))?;
51        let hash = info["git"]["sha1"].as_str().ok_or_else(|| {
52            format!("Failed to parse .cargo_vcs_info.json: no `.git.sha` field: {info:?}")
53        })?;
54        println!("cargo:rustc-env={FEDIMINT_BUILD_CODE_VERSION_ENV}={hash}");
55        return Ok(());
56    }
57
58    for base in [
65        ".",
68        "..",
69        "../..",
70        "../../..",
71        "../../../..",
72        "../../../../..",
73    ] {
74        let p = &format!("{base}/.git/HEAD");
75        if Path::new(&p).exists() {
76            println!("cargo:rerun-if-changed={p}");
77        }
78        let p = &format!("{base}/HEAD");
80        if Path::new(&p).exists() {
81            println!("cargo:rerun-if-changed={p}");
82        }
83    }
84
85    let hash = call_cmd("git", &["rev-parse", "HEAD"])?;
86
87    let dirty = !call_cmd("git", &["status", "--porcelain"])?.is_empty();
88
89    let hash = if dirty {
90        format!("{}00000000{}", &hash[0..16], &hash[(40 - 16)..40])
95    } else {
96        hash
97    };
98
99    println!("cargo:rustc-env={FEDIMINT_BUILD_CODE_VERSION_ENV}={hash}");
100
101    Ok(())
102}
103
104fn call_cmd(cmd: &str, args: &[&str]) -> Result<String, String> {
105    let output = match Command::new(cmd).args(args).output() {
106        Ok(output) => output,
107        Err(e) => {
108            return Err(format!("Failed to execute `git` command: {e}"));
109        }
110    };
111
112    if !output.status.success() {
113        return Err(format!(
114            "`git` command failed: stderr: {}; stdout: {}",
115            String::from_utf8_lossy(&output.stderr),
116            String::from_utf8_lossy(&output.stdout)
117        ));
118    }
119
120    match String::from_utf8(output.stdout) {
121        Ok(o) => Ok(o.trim().to_string()),
122        Err(e) => Err(format!("Invalid UTF-8 sequence detected: {e}")),
123    }
124}
125
126pub fn set_code_version() {
129    if let Err(e) = set_code_version_inner() {
130        eprintln!("Failed to detect git hash version: {e}. Set {FORCE_GIT_HASH_ENV} to enforce the version and skip auto-detection.");
131        println!("cargo:rustc-env={FEDIMINT_BUILD_CODE_VERSION_ENV}=0000000000000000000000000000000000000000");
132    }
133}