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

nordisk/znippy-zoomies

osm-katana/build.rs

//! osm-katana's build script. Two jobs:
//!  1. Emit `OSM_KATANA_GIT_HASH` — the short git SHA the build was cut from, read
//!     PURE-FS (no `git` process) from the checkout's own plumbing. This is korp's
//!     `build_provenance::head_sha` pattern (`korp/src/build_provenance.rs`), the one
//!     pattern the build-version law asks for: `osm-katana --version` prints
//!     `osm-katana v<semver> (<short-hash>)`, exactly the shape `korp --version` has.
//!     Handles a symbolic or detached `HEAD`, a loose or packed ref, and a linked
//!     worktree (`.git` is a `gitdir:` file, refs live in the common dir). Degrades to
//!     `unknown` — never fails the build — for a source tarball with no `.git`.
//!  2. Generate the protobuf modules for the PBF reader.

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

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

    let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap_or_default());
    let (hash, watch) = head_sha(&manifest_dir);
    for p in watch {
        println!("cargo:rerun-if-changed={}", p.display());
    }
    println!("cargo:rustc-env=OSM_KATANA_GIT_HASH={hash}");

    protobuf_codegen::Codegen::new()
        .pure()
        .out_dir(&out)
        .inputs(["proto/fileformat.proto", "proto/osmformat.proto"])
        .include("proto")
        .run()?;

    // Write a mod.rs so that `include!(OUT_DIR/proto_mod.rs)` can declare both
    // generated modules and Rust will resolve them relative to OUT_DIR.
    std::fs::File::create(format!("{out}/proto_mod.rs"))?
        .write_all(b"pub mod fileformat;\npub mod osmformat;\n")?;

    println!("cargo:rerun-if-changed=proto/fileformat.proto");
    println!("cargo:rerun-if-changed=proto/osmformat.proto");

    Ok(())
}

// ── THE BUILD STAMP (korp's `head_sha`, verbatim in shape) ───────────────────
//
// Read the short SHA of `HEAD` from the git plumbing under `root`, plus the files a
// change to it would move (for `rerun-if-changed`). `"unknown"` when there is no
// checkout, or nothing resolvable — never an error, a tarball build must still build.

fn head_sha(root: &Path) -> (String, Vec<PathBuf>) {
    let mut watch = Vec::new();
    let Some(git) = resolve_git_dir(root) else {
        return ("unknown".into(), watch);
    };
    let common = common_dir(&git);
    watch.push(git.join("HEAD"));
    watch.push(common.join("packed-refs"));

    let Ok(head) = std::fs::read_to_string(git.join("HEAD")) else {
        return ("unknown".into(), watch);
    };
    let head = head.trim();
    let full = match head.strip_prefix("ref:") {
        Some(ref_name) => {
            let ref_name = ref_name.trim();
            let loose = git.join(ref_name);
            let loose_common = common.join(ref_name);
            watch.push(loose.clone());
            watch.push(loose_common.clone());
            match std::fs::read_to_string(&loose)
                .ok()
                .or_else(|| std::fs::read_to_string(&loose_common).ok())
            {
                Some(s) => s.trim().to_string(),
                None => match resolve_packed(&common.join("packed-refs"), ref_name) {
                    Some(s) => s,
                    None => return ("unknown".into(), watch),
                },
            }
        }
        None => head.to_string(),
    };
    if full.len() < 7 {
        return ("unknown".into(), watch);
    }
    (full.chars().take(10).collect(), watch)
}

/// The git directory for a checkout at (or above) `root`: a `.git` DIRECTORY
/// normally, or the `gitdir:` a `.git` FILE points at for a linked worktree.
/// osm-katana is a workspace member, so the checkout root is a parent of the crate.
fn resolve_git_dir(root: &Path) -> Option<PathBuf> {
    let mut dir = Some(root);
    while let Some(d) = dir {
        let dot = d.join(".git");
        if dot.is_dir() {
            return Some(dot);
        }
        if dot.is_file() {
            let content = std::fs::read_to_string(&dot).ok()?;
            let rel = content.strip_prefix("gitdir:")?.trim();
            let p = Path::new(rel);
            return Some(if p.is_absolute() { p.to_path_buf() } else { d.join(p) });
        }
        dir = d.parent();
    }
    None
}

/// The shared common git dir: for a linked worktree, `gitdir/commondir` points at
/// the repo's real git dir (where `packed-refs` and the shared refs live).
fn common_dir(gitdir: &Path) -> PathBuf {
    match std::fs::read_to_string(gitdir.join("commondir")) {
        Ok(s) => {
            let rel = s.trim();
            let p = Path::new(rel);
            if p.is_absolute() { p.to_path_buf() } else { gitdir.join(p) }
        }
        Err(_) => gitdir.to_path_buf(),
    }
}

/// Resolve `ref_name` out of a `packed-refs` file (`<sha> refs/heads/foo`), skipping
/// comment (`#`) and peeled-tag (`^`) lines.
fn resolve_packed(packed: &Path, ref_name: &str) -> Option<String> {
    let content = std::fs::read_to_string(packed).ok()?;
    for line in content.lines() {
        let line = line.trim();
        if line.is_empty() || line.starts_with('#') || line.starts_with('^') {
            continue;
        }
        if let Some((sha, name)) = line.split_once(' ')
            && name.trim() == ref_name
        {
            return Some(sha.trim().to_string());
        }
    }
    None
}