xbp 10.46.0

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
//! Embed install-source + enabled feature metadata for `xbp -v` / `--version`.

use std::env;
use std::path::Path;
use std::process::Command;

/// Known optional/default features of the `xbp` package (display order).
const KNOWN_FEATURES: &[&str] = &[
    "secrets",
    "linear",
    "athena",
    "docker",
    "kafka",
    "kubernetes",
    "monitoring",
    "nordvpn",
    "openapi-gen",
    "systemd",
];

fn main() {
    let manifest_dir: String = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
    let version: String = env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.0.0".to_string());
    let repository: String =
        env::var("CARGO_PKG_REPOSITORY").unwrap_or_else(|_| "https://github.com/xylex-group/xbp".to_string());

    let (kind, detail, git_url, git_rev) = detect_install_source(&manifest_dir, &version, &repository);
    let features = collect_enabled_features();
    let features_csv = features.join(",");
    let features_plus = format_features_plus(&features);

    println!("cargo:rustc-env=XBP_INSTALL_SOURCE_KIND={kind}");
    println!("cargo:rustc-env=XBP_INSTALL_SOURCE_DETAIL={detail}");
    println!("cargo:rustc-env=XBP_INSTALL_GIT_URL={git_url}");
    println!("cargo:rustc-env=XBP_INSTALL_GIT_REV={git_rev}");
    println!("cargo:rustc-env=XBP_ENABLED_FEATURES={features_csv}");
    println!("cargo:rustc-env=XBP_ENABLED_FEATURES_PLUS={features_plus}");
    println!("cargo:rustc-env=XBP_PKG_REPOSITORY={repository}");

    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=Cargo.toml");
    println!("cargo:rerun-if-env-changed=XBP_INSTALL_SOURCE");
    println!("cargo:rerun-if-env-changed=XBP_INSTALL_GIT_URL");
    println!("cargo:rerun-if-env-changed=XBP_INSTALL_GIT_REV");
}

fn collect_enabled_features() -> Vec<String> {
    let mut enabled = Vec::new();
    for feature in KNOWN_FEATURES {
        let env_key = format!(
            "CARGO_FEATURE_{}",
            feature.to_ascii_uppercase().replace('-', "_")
        );
        if env::var_os(&env_key).is_some() {
            enabled.push((*feature).to_string());
        }
    }
    enabled
}

/// `secrets,linear +athena +docker` — default set then optional with `+`.
fn format_features_plus(features: &[String]) -> String {
    const DEFAULT: &[&str] = &["secrets", "linear"];
    let mut base = Vec::new();
    let mut extra = Vec::new();
    for feature in features {
        if DEFAULT.contains(&feature.as_str()) {
            base.push(feature.clone());
        } else {
            extra.push(format!("+{feature}"));
        }
    }
    // Preserve package default order for base features.
    base.sort_by_key(|f| DEFAULT.iter().position(|d| d == f).unwrap_or(usize::MAX));
    let mut out = base.join(",");
    if !extra.is_empty() {
        if !out.is_empty() {
            out.push(' ');
        }
        out.push_str(&extra.join(" "));
    }
    if out.is_empty() {
        "(none)".to_string()
    } else {
        out
    }
}

fn detect_install_source(
    manifest_dir: &str,
    version: &str,
    repository: &str,
) -> (String, String, String, String) {
    // Explicit override for packaging / CI:
    //   XBP_INSTALL_SOURCE=crates.io|git|path
    //   XBP_INSTALL_GIT_URL=https://...
    //   XBP_INSTALL_GIT_REV=abc1234
    if let Ok(forced_kind) = env::var("XBP_INSTALL_SOURCE") {
        let kind = forced_kind.trim().to_ascii_lowercase();
        let git_url: String = env::var("XBP_INSTALL_GIT_URL").unwrap_or_default();
        let git_rev: String = env::var("XBP_INSTALL_GIT_REV").unwrap_or_default();
        let detail = match kind.as_str() {
            "crates.io" | "crates" | "registry" => format!("xbp@{version}"),
            "git" => {
                let url = if git_url.is_empty() {
                    repository.to_string()
                } else {
                    git_url.clone()
                };
                if git_rev.is_empty() {
                    url
                } else {
                    format!("{url}#{git_rev}")
                }
            }
            _ => manifest_dir.to_string(),
        };
        let kind = match kind.as_str() {
            "crates" | "registry" => "crates.io".to_string(),
            other => other.to_string(),
        };
        return (kind, detail, git_url, git_rev);
    }

    let path = Path::new(manifest_dir);
    let normalized = path.to_string_lossy().replace('\\', "/").to_ascii_lowercase();

    // crates.io / sparse registry checkout
    if normalized.contains("/registry/src/")
        || normalized.contains("index.crates.io")
        || normalized.contains("crates.io-")
    {
        return (
            "crates.io".to_string(),
            format!("xbp@{version}"),
            String::new(),
            String::new(),
        );
    }

    // cargo install --git checkouts live under ~/.cargo/git/checkouts/<repo>-<hash>/...
    if normalized.contains("/git/checkouts/") {
        let git_url = git_remote_url(path)
            .or_else(|| git_remote_url(&path.join("..")))
            .unwrap_or_else(|| repository.to_string());
        let git_rev = git_rev_short(path).unwrap_or_default();
        let detail = if git_rev.is_empty() {
            git_url.clone()
        } else {
            format!("{git_url}#{git_rev}")
        };
        return ("git".to_string(), detail, git_url, git_rev);
    }

    // Local path / workspace build (cargo install --path, cargo run, dev)
    let git_url = git_remote_url(path)
        .or_else(|| find_git_remote_upwards(path))
        .unwrap_or_default();
    let git_rev = git_rev_short(path)
        .or_else(|| find_git_rev_upwards(path))
        .unwrap_or_default();
    if !git_url.is_empty() {
        let detail = if git_rev.is_empty() {
            format!("{git_url} (path)")
        } else {
            format!("{git_url}#{git_rev} (path)")
        };
        return ("path".to_string(), detail, git_url, git_rev);
    }

    (
        "path".to_string(),
        manifest_dir.to_string(),
        String::new(),
        String::new(),
    )
}

fn find_git_remote_upwards(start: &Path) -> Option<String> {
    for ancestor in start.ancestors().take(6) {
        if let Some(url) = git_remote_url(ancestor) {
            return Some(url);
        }
    }
    None
}

fn find_git_rev_upwards(start: &Path) -> Option<String> {
    for ancestor in start.ancestors().take(6) {
        if let Some(rev) = git_rev_short(ancestor) {
            return Some(rev);
        }
    }
    None
}

fn git_remote_url(dir: &Path) -> Option<String> {
    if !dir.join(".git").exists() && !dir_is_inside_git_worktree(dir) {
        // Still try — git can resolve from nested dirs.
    }
    let output = Command::new("git")
        .args(["remote", "get-url", "origin"])
        .current_dir(dir)
        .output()
        .ok()?;
    if !output.status.success() {
        return None;
    }
    let url = String::from_utf8_lossy(&output.stdout).trim().to_string();
    if url.is_empty() {
        None
    } else {
        Some(normalize_git_url(&url))
    }
}

fn git_rev_short(dir: &Path) -> Option<String> {
    let output = Command::new("git")
        .args(["rev-parse", "--short", "HEAD"])
        .current_dir(dir)
        .output()
        .ok()?;
    if !output.status.success() {
        return None;
    }
    let rev = String::from_utf8_lossy(&output.stdout).trim().to_string();
    if rev.is_empty() {
        None
    } else {
        Some(rev)
    }
}

fn dir_is_inside_git_worktree(dir: &Path) -> bool {
    Command::new("git")
        .args(["rev-parse", "--is-inside-work-tree"])
        .current_dir(dir)
        .output()
        .map(|out| out.status.success())
        .unwrap_or(false)
}

fn normalize_git_url(url: &str) -> String {
    let trimmed = url.trim();
    // git@github.com:org/repo.git → https://github.com/org/repo
    if let Some(rest) = trimmed.strip_prefix("git@github.com:") {
        let rest = rest.trim_end_matches(".git");
        return format!("https://github.com/{rest}");
    }
    if let Some(rest) = trimmed.strip_prefix("ssh://git@github.com/") {
        let rest = rest.trim_end_matches(".git");
        return format!("https://github.com/{rest}");
    }
    trimmed.trim_end_matches(".git").to_string()
}