lmjtfy.git / apps / lmjtfy / build.rs
1//! `LMJTFY_BUILD`: the commit the Worker is built from, with `-dirty` if the
2//! tree had changes, or `unknown` outside git. Every page carries it, and the
3//! archive object tells each page that connects what it is now: a page whose
4//! build differs is older than the site, and offers a reload.
5//!
6//! `LMJTFY_NOTE`: the built commit's `Release-Note:` trailer, or empty. A page
7//! from an earlier build is shown it as a toast when it reconnects after the
8//! deploy, so the people on the site hear what changed.
9
10//!
11//! `icons.rs` (in `OUT_DIR`): every picture in third-party/jerrys-pixel-icons
12//! by name, for the Worker to serve at `/icons/<name>.png`.
13
14use std::process::Command;
15
16fn git(args: &[&str]) -> Option<String> {
17    let output = Command::new("git").args(args).output().ok()?;
18    output.status.success().then(|| String::from_utf8_lossy(&output.stdout).trim().to_owned())
19}
20
21fn main() {
22    let commit = git(&["rev-parse", "--short=12", "HEAD"]);
23    let dirty = git(&["status", "--porcelain"]).is_some_and(|status| !status.is_empty());
24    let build = match commit {
25        Some(commit) if dirty => format!("{commit}-dirty"),
26        Some(commit) => commit,
27        None => "unknown".to_owned(),
28    };
29    println!("cargo:rustc-env=LMJTFY_BUILD={build}");
30    let note = git(&["log", "-1", "--format=%(trailers:key=Release-Note,valueonly,unfold)"]).unwrap_or_default();
31    println!("cargo:rustc-env=LMJTFY_NOTE={}", note.lines().next().unwrap_or_default().trim());
32    icons();
33    // A new commit, or a change to what is staged, is a new build.
34    let root = git(&["rev-parse", "--show-toplevel"]).unwrap_or_default();
35    for path in [".git/HEAD", ".git/index"] {
36        println!("cargo:rerun-if-changed={root}/{path}");
37    }
38    if let Some(head) = git(&["symbolic-ref", "-q", "HEAD"]) {
39        println!("cargo:rerun-if-changed={root}/.git/{head}");
40    }
41}
42
43/// Writes the table of icons: `("file-rust", include_bytes!(...))` for each
44/// PNG in the folder, sorted, so a name is found by binary search.
45fn icons() {
46    let folder = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../third-party/jerrys-pixel-icons");
47    println!("cargo:rerun-if-changed={}", folder.display());
48    let mut names: Vec<String> = std::fs::read_dir(&folder)
49        .expect("third-party/jerrys-pixel-icons is there")
50        .filter_map(|entry| entry.ok()?.file_name().into_string().ok())
51        .filter_map(|name| name.strip_suffix(".png").map(str::to_owned))
52        .collect();
53    names.sort();
54    let rows: String = names.iter().map(|name| format!("    ({name:?}, include_bytes!({:?})),\n", folder.join(format!("{name}.png")))).collect();
55    let out = std::path::Path::new(&std::env::var("OUT_DIR").expect("cargo sets OUT_DIR")).join("icons.rs");
56    std::fs::write(out, format!("pub static ICONS: &[(&str, &[u8])] = &[\n{rows}];\n")).expect("OUT_DIR can be written");
57}