mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-30 21:56:53 -06:00
env! fixes the path to contributors/ when the build script is compiled. After a checkout is moved or copied, the compiled script keeps looking in the old location: the About window loses its contributor list, and because the rerun-if-changed path no longer exists, Cargo reruns the script and rebuilds the UI crate on every build. Reading the variable at run time follows the checkout.
109 lines
4.6 KiB
Rust
109 lines
4.6 KiB
Rust
//! Bake the contributor credits into the binary as static Rust tables (craftrules
|
|
//! standards/contributors.md). Nothing is read at run time.
|
|
//!
|
|
//! - contributors/contributors.json: commit stats per GitHub username, generated by craftrules
|
|
//! scripts/contributors.py, which also marks whose people.toml entry it verified.
|
|
//! - contributors/people.toml: the names people chose for themselves. A name is used only when the
|
|
//! JSON says the entry was verified (`people_entry`).
|
|
//!
|
|
//! A missing or malformed file gives empty tables (or no names) and a warning.
|
|
|
|
use std::fmt::Write as _;
|
|
|
|
use serde_json::Value;
|
|
|
|
fn main() {
|
|
// Read when the script runs: `env!` would keep the path from when the script was compiled,
|
|
// which goes stale if the checkout moves (and a missing file reruns the script every build).
|
|
let manifest = std::env::var_os("CARGO_MANIFEST_DIR").map(std::path::PathBuf::from).unwrap_or_default();
|
|
let dir = manifest.join("../../contributors");
|
|
let json = read(&dir.join("contributors.json"), "the About window shows no contributors", |t| {
|
|
serde_json::from_str::<Value>(t).map_err(|e| e.to_string())
|
|
})
|
|
.unwrap_or(Value::Null);
|
|
let people = read(&dir.join("people.toml"), "contributors are shown by username only", |t| t.parse::<toml::Table>().map_err(|e| e.to_string()))
|
|
.unwrap_or_default();
|
|
let out = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap_or_default()).join("credits.rs");
|
|
if let Err(e) = std::fs::write(&out, generate(&json, &people)) {
|
|
println!("cargo::warning=writing {}: {e}", out.display());
|
|
}
|
|
}
|
|
|
|
fn read<T>(path: &std::path::Path, fallback: &str, parse: impl Fn(&str) -> Result<T, String>) -> Option<T> {
|
|
println!("cargo::rerun-if-changed={}", path.display());
|
|
let r = std::fs::read_to_string(path).map_err(|e| e.to_string()).and_then(|t| parse(&t));
|
|
if let Err(e) = &r {
|
|
println!("cargo::warning={}: {e}; {fallback}", path.display());
|
|
}
|
|
r.ok()
|
|
}
|
|
|
|
/// A non-empty string field of someone's people.toml entry, as a Rust `Option<&str>` literal.
|
|
fn name(v: Option<&str>) -> String {
|
|
match v.map(str::trim).filter(|s| !s.is_empty()) {
|
|
Some(x) => format!("Some({x:?})"),
|
|
None => "None".into(),
|
|
}
|
|
}
|
|
|
|
fn s(v: &Value, k: &str) -> String {
|
|
format!("{:?}", v.get(k).and_then(Value::as_str).unwrap_or(""))
|
|
}
|
|
|
|
fn n(v: &Value, k: &str) -> u64 {
|
|
v.get(k).and_then(Value::as_u64).unwrap_or(0)
|
|
}
|
|
|
|
fn rows(json: &Value, k: &str) -> Vec<Value> {
|
|
json.get(k).and_then(Value::as_array).cloned().unwrap_or_default()
|
|
}
|
|
|
|
fn generate(json: &Value, people: &toml::Table) -> String {
|
|
let mut o = String::new();
|
|
let _ = writeln!(o, "pub static TOTAL_COMMITS: u64 = {};", n(json, "commits"));
|
|
let _ = writeln!(o, "pub static CONTRIBUTORS: &[Contributor] = &[");
|
|
for c in rows(json, "contributors") {
|
|
let login = c.get("login").and_then(Value::as_str).unwrap_or("");
|
|
if login.is_empty() {
|
|
continue;
|
|
}
|
|
// Names only from a verified, self-chosen people.toml entry.
|
|
let entry = people.get("people").and_then(|p| p.get(login)).filter(|_| c.get("people_entry").and_then(Value::as_bool) == Some(true));
|
|
let field = |k: &str| entry.and_then(|e| e.get(k)).and_then(toml::Value::as_str);
|
|
let synced = entry.and_then(|e| e.get("sync_github_name")).and_then(toml::Value::as_bool) == Some(true);
|
|
let display = field("display_name")
|
|
.filter(|s| !s.trim().is_empty())
|
|
.or_else(|| if synced { c.get("github_name").and_then(Value::as_str) } else { None });
|
|
let _ = writeln!(
|
|
o,
|
|
" Contributor {{ login: {login:?}, display_name: {}, real_name: {}, prs: {}, commits: {}, lines_added: {}, lines_deleted: {}, binary_added: {}, binary_deleted: {}, first_commit: {}, last_commit: {} }},",
|
|
name(display),
|
|
name(field("real_name")),
|
|
n(&c, "prs"),
|
|
n(&c, "commits"),
|
|
n(&c, "lines_added"),
|
|
n(&c, "lines_deleted"),
|
|
n(&c, "binary_added"),
|
|
n(&c, "binary_deleted"),
|
|
s(&c, "first_commit"),
|
|
s(&c, "last_commit"),
|
|
);
|
|
}
|
|
let _ = writeln!(o, "];");
|
|
let _ = writeln!(o, "pub static MODELS: &[Model] = &[");
|
|
for m in rows(json, "models") {
|
|
let _ = writeln!(
|
|
o,
|
|
" Model {{ company: {}, model: {}, version: {}, commits: {}, lines_added: {}, lines_deleted: {} }},",
|
|
s(&m, "company"),
|
|
s(&m, "model"),
|
|
s(&m, "version"),
|
|
n(&m, "commits"),
|
|
n(&m, "lines_added"),
|
|
n(&m, "lines_deleted"),
|
|
);
|
|
}
|
|
let _ = writeln!(o, "];");
|
|
o
|
|
}
|