Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

holger-estate-front/build.rs

//! Build script for `holger-estate-front`. Pure Rust, no subprocess.
//!
//! 1. **holger's AdminService client**, from holger's OWN `proto/holger.proto`
//!    (protox + tonic-prost-build, no `protoc`) — the certification courier
//!    speaks exactly the contract the boxes serve (`src/certify.rs`).
//! 2. **The build's commit** (`HOLGER_FRONT_GIT_HASH`), read from the on-disk
//!    git plumbing — the same reader as `server/lib/build.rs` (copied, a build
//!    script cannot link another's) — offered to the installer as `version`.
//! 3. **The built console** (feature `console`): holger-ui's built bundle embedded,
//!    so the medium stays one binary (korp-start execs exactly one file) —
//!    `HOLGER_FRONT_CONSOLE_DIST`, else the declared sibling: the METERED bundle
//!    `../../holger-ui/dist-metered` under `metered` (holger-ui's row
//!    `mvp-ui-web-metered`, with the sign-up, welcome and order pages its buy routes
//!    serve), else the open `../../holger-ui/dist` (A28 M13). An absent console is NOT a build failure (the
//!    tests build without it): the binary then knows it (`DIST_SOURCE` is
//!    `None`), serves only the pass-through to holger, and says so; a `cargo:warning` says so here. (gunnar-front's build.rs pattern.)

use std::fmt::Write as _;
use std::fs;
use std::path::{Path, PathBuf};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let manifest = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
    let out = PathBuf::from(std::env::var("OUT_DIR")?);

    // 1. The AdminService client.
    let include = manifest.join("../proto");
    let proto = include.join("holger.proto");
    rerun_if_changed(&proto);
    let descriptors = protox::compile([&proto], [&include])?;
    tonic_prost_build::configure()
        .build_server(false)
        .build_client(true)
        .skip_protoc_run()
        .compile_fds(descriptors)?;

    // 2. The commit.
    let hash = git_short_hash().unwrap_or_else(|| "unknown".to_string());
    println!("cargo:rustc-env=HOLGER_FRONT_GIT_HASH={hash}");

    // 3. The console.
    println!("cargo:rerun-if-env-changed=HOLGER_FRONT_CONSOLE_DIST");
    let console = std::env::var_os("CARGO_FEATURE_CONSOLE").is_some();
    let declared = manifest.join(if std::env::var_os("CARGO_FEATURE_METERED").is_some() {
        "../../holger-ui/dist-metered"
    } else {
        "../../holger-ui/dist"
    });
    let dist = std::env::var_os("HOLGER_FRONT_CONSOLE_DIST")
        .map(PathBuf::from)
        .unwrap_or_else(|| declared.clone());
    let mut files = Vec::new();
    if console {
        if let Some(parent) = dist.parent() {
            println!("cargo:rerun-if-changed={}", parent.display());
        }
        println!("cargo:rerun-if-changed={}", dist.display());
        if dist.join("index.html").is_file() {
            walk(&dist, &dist, &mut files);
            for (_, abs) in &files {
                println!("cargo:rerun-if-changed={}", abs.display());
            }
        } else {
            println!("cargo:warning=holger-estate-front: no built console at {} (index.html absent) — this binary serves the console's API and /login, not the bundle; `cargo xtask estate-media` refuses a front medium without it ([estate-media-front-no-console])", dist.display());
        }
    }
    let mut code = String::new();
    let _ = writeln!(
        code,
        "/// The built console's files, `(path, bytes)`.\npub static FILES: &[(&str, &[u8])] = &["
    );
    for (rel, abs) in &files {
        let _ = writeln!(
            code,
            "    ({rel:?}, include_bytes!({:?})),",
            abs.display().to_string()
        );
    }
    let _ = writeln!(code, "];");
    let source = (!files.is_empty()).then(|| {
        dist.canonicalize()
            .unwrap_or(dist.clone())
            .display()
            .to_string()
    });
    let _ = writeln!(code, "/// Where they were read from at build time; `None` = not embedded.\npub const DIST_SOURCE: Option<&str> = {source:?};");
    let _ = writeln!(code, "/// The declared sibling this build reads when `HOLGER_FRONT_CONSOLE_DIST` is unset.\npub const DECLARED: &str = {:?};", declared.display().to_string());
    fs::write(out.join("console_dist.rs"), code)?;
    Ok(())
}

fn walk(dir: &Path, base: &Path, out: &mut Vec<(String, PathBuf)>) {
    let Ok(rd) = fs::read_dir(dir) else { return };
    let mut entries: Vec<_> = rd.flatten().collect();
    entries.sort_by_key(|e| e.file_name());
    for e in entries {
        let p = e.path();
        if p.is_dir() {
            walk(&p, base, out);
        } else if let Ok(rel) = p.strip_prefix(base) {
            out.push((
                rel.to_string_lossy().replace('\\', "/"),
                p.canonicalize().unwrap_or(p.clone()),
            ));
        }
    }
}

/// Best-effort short commit hash, reading only the on-disk git plumbing.
fn git_short_hash() -> Option<String> {
    let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").ok()?);
    let git_dir = find_git_dir(&manifest_dir)?;
    let common_dir = common_dir(&git_dir);

    let head_path = git_dir.join("HEAD");
    rerun_if_changed(&head_path);
    let head = fs::read_to_string(&head_path).ok()?;
    let head = head.trim();

    let full = if let Some(refname) = head.strip_prefix("ref:") {
        let refname = refname.trim();
        resolve_ref(refname, &git_dir, &common_dir)?
    } else {
        // Detached HEAD: the file holds the commit id directly.
        head.to_string()
    };

    let full = full.trim();
    if full.is_empty() {
        return None;
    }
    // Short sha, matching `git rev-parse --short` default width.
    Some(full.chars().take(7).collect())
}

/// Walk up from `start` until a `.git` directory or `.git` gitdir-file is found.
fn find_git_dir(start: &Path) -> Option<PathBuf> {
    let mut dir = Some(start);
    while let Some(d) = dir {
        let dot_git = d.join(".git");
        if dot_git.is_dir() {
            return Some(dot_git);
        }
        if dot_git.is_file() {
            // Linked worktree: `.git` is a file `gitdir: <path>`.
            let contents = fs::read_to_string(&dot_git).ok()?;
            let target = contents.trim().strip_prefix("gitdir:")?.trim();
            let target = d.join(target);
            return Some(normalize(&target));
        }
        dir = d.parent();
    }
    None
}

/// The shared common git dir for a (possibly linked-worktree) git dir. Worktrees
/// carry a `commondir` pointer; the main working copy is its own common dir.
fn common_dir(git_dir: &Path) -> PathBuf {
    let commondir_file = git_dir.join("commondir");
    if let Ok(contents) = fs::read_to_string(&commondir_file) {
        let rel = contents.trim();
        return normalize(&git_dir.join(rel));
    }
    git_dir.to_path_buf()
}

/// Resolve a `refs/…` name to a commit id: loose ref file first (worktree dir,
/// then common dir), then `packed-refs`.
fn resolve_ref(refname: &str, git_dir: &Path, common_dir: &Path) -> Option<String> {
    for base in [git_dir, common_dir] {
        let loose = base.join(refname);
        if loose.is_file() {
            rerun_if_changed(&loose);
            if let Ok(s) = fs::read_to_string(&loose) {
                let s = s.trim();
                if !s.is_empty() {
                    return Some(s.to_string());
                }
            }
        }
    }

    // packed-refs lives in the common dir.
    let packed = common_dir.join("packed-refs");
    if packed.is_file() {
        rerun_if_changed(&packed);
        if let Ok(contents) = fs::read_to_string(&packed) {
            for line in contents.lines() {
                let line = line.trim();
                if line.is_empty() || line.starts_with('#') || line.starts_with('^') {
                    continue;
                }
                if let Some((sha, name)) = line.split_once(' ') {
                    if name.trim() == refname {
                        return Some(sha.trim().to_string());
                    }
                }
            }
        }
    }
    None
}

/// Lightweight lexical `.`/`..` normalization (avoids `canonicalize`, which fails
/// on not-yet-existing paths and would resolve symlinks).
fn normalize(path: &Path) -> PathBuf {
    let mut out = PathBuf::new();
    for comp in path.components() {
        match comp {
            std::path::Component::ParentDir => {
                out.pop();
            }
            std::path::Component::CurDir => {}
            other => out.push(other.as_os_str()),
        }
    }
    out
}

fn rerun_if_changed(path: &Path) {
    println!("cargo:rerun-if-changed={}", path.display());
}