chaud-cli 0.1.0

Utilities for working with Chaud, a hot-reloading library for Cargo workspaces.
Documentation
#![allow(
    clippy::missing_errors_doc,
    reason = "less restrictions on build-time tools"
)]
use anyhow::{Context as _, Result, bail};
use chaud_cli::{actual_args, link_args, run};
use std::env;
use std::process::Command;

fn main() -> Result<()> {
    let args = actual_args()?;
    let Some((prog, args)) = args.split_first() else {
        bail!("Too few arguments: missing `rustc` executable");
    };

    let mut cmd = Command::new(prog);
    cmd.args(args);

    if is_hot(args) {
        let extracted = extract(args).context("Failed to extract feature flags")?;

        if extracted.is_binary {
            cmd.arg("-Clink-dead-code")
                .args(link_args()?)
                .env("RUSTC_BOOTSTRAP", "1");
        }

        if env::var_os("CHAUD_FEATURE_FLAGS").is_none() {
            cmd.env("__CHAUD_RUSTC_FEATURE_FLAGS", extracted.feature_flags);
        }
    }

    run(cmd)
}

fn is_hot(args: &[String]) -> bool {
    args.iter().any(|s| s.contains("__chaud_hot_marker"))
}

struct Extracted {
    is_binary: bool,
    feature_flags: String,
}

fn extract(args: &[String]) -> Result<Extracted> {
    use lexopt::prelude::*;

    let mut parser = lexopt::Parser::from_args(args);

    let mut is_binary = false;
    let mut features = vec![];
    while let Some(arg) = parser.next()? {
        match arg {
            Long("crate-type") => {
                let val = parser.value()?.parse::<String>()?;
                is_binary |= val == "bin";
            }
            Long("cfg") => {
                let val = parser.value()?.parse::<String>()?;
                let Some(f) = val.strip_prefix("feature=") else {
                    continue;
                };
                let f = f.trim_matches('"');
                features.push(f.to_owned());
            }
            Short(_) | Long(_) => {
                parser.optional_value();
            }
            _ => {}
        }
    }

    let feature_flags = match features.as_slice() {
        [] => String::new(),
        f => format!("-F{}", f.join(",")),
    };

    Ok(Extracted { is_binary, feature_flags })
}