midenup 1.1.0

The Miden toolchain manager
use std::process::Command;

use cargo_toml::Manifest;

fn main() {
    let toml_contents = std::fs::read("Cargo.toml").expect("Failed to find Cargo.toml");
    let manifest =
        Manifest::from_slice(&toml_contents).expect("Failed to parse Cargo.toml as Manifest");
    let curl_version = {
        let curl = manifest
            .dependencies
            .get("curl")
            .expect("Couldn't find curl in parsed manifest");
        match curl {
            cargo_toml::Dependency::Simple(ver) => ver,
            cargo_toml::Dependency::Detailed(detail) => &detail.version.as_ref().unwrap().clone(),
            // This case should not happen since we are reading the root Cargo.toml
            cargo_toml::Dependency::Inherited(_) => {
                panic!("Couldn't determine curl version since it appears as inherited.")
            },
        }
    };
    // Pass the $CURL_VERSION to the crate at compile time.
    println!("cargo:rustc-env=CURL_VERSION={}", curl_version);

    // Pass the $TARGET to the crate at compile time.
    println!(
        "cargo:rustc-env=TARGET={}",
        std::env::var("TARGET").expect("Failed to obtain $TARGET triple constant.")
    );

    // Generated by cargo at runtime when the build script is run:
    // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
    let build_script = std::env::var("OUT_DIR").unwrap();

    write_command_to_file(&["cargo", "--version"], &(build_script.clone() + "/cargo_version.in"));
    std::fs::write(build_script + "/git_revision.in", git_revision())
        .expect("Failed to write git_revision.in");
}

/// The commit this build comes from: `HEAD` in a checkout, the sha recorded by `cargo publish` in
/// a crates.io tarball, or `unknown`.
fn git_revision() -> String {
    println!("cargo:rerun-if-changed=.git/HEAD");
    println!("cargo:rerun-if-changed=.git/index");
    println!("cargo:rerun-if-changed=.cargo_vcs_info.json");

    let from_git = Command::new("git")
        .args(["rev-parse", "--verify", "HEAD"])
        .output()
        .ok()
        .filter(|output| output.status.success())
        .and_then(|output| String::from_utf8(output.stdout).ok())
        .map(|sha| sha.trim().to_string())
        .filter(|sha| !sha.is_empty());

    let from_vcs_info = || {
        let contents = std::fs::read_to_string(".cargo_vcs_info.json").ok()?;
        let info: serde_json::Value = serde_json::from_str(&contents).ok()?;
        info["git"]["sha1"].as_str().map(str::to_string)
    };

    from_git.or_else(from_vcs_info).unwrap_or_else(|| "unknown".to_string())
}

fn write_command_to_file(command: &[&str], file: &str) {
    let full_command =
        command.iter().fold(String::new(), |acc, argument| format!("{acc} {argument}"));

    let output = {
        let command =
            Command::new(command.first().expect("command must have at leaste one element"))
                .args(command.iter().skip(1))
                .output()
                .unwrap_or_else(|err| {
                    panic!("Couldn't run {full_command} because of: {err}");
                })
                .stdout;

        String::from_utf8(command).unwrap_or_else(|err| {
            panic!("failed to parse {full_command} output as string, because of {err}")
        })
    };

    std::fs::write(file, output.trim())
        .unwrap_or_else(|err| panic!("Failed to write to {file}: {err}"));
}