examples/release_demo_znippy_holger.rs
//! End-to-end nornir release demo against znippy + holger.
//!
//! cargo run --release --example release_demo_znippy_holger
//!
//! Pipeline (per repo, in dep-graph order):
//! 1. pre-flight dirty check (refuses if any repo dirty)
//! 2. `cargo test --workspace --no-fail-fast --quiet`
//! 3. `cargo run -p xtask --release --example nornir-bench --quiet`
//! → parse last stdout line as a BenchRun
//! → persist into Urðr (`bench_runs` + `bench_results`)
//! 4. record per-repo row into `release_lineage`
//!
//! Override repo locations via env: ZNIPPY_PATH=… HOLGER_PATH=…
//! Wipe the demo warehouse first with: FRESH_WAREHOUSE=1
use std::path::PathBuf;
use anyhow::{Context, Result};
use nornir::release::pipeline::{
PipelineOptions, ReleaseReport, query_release_history, run_pipeline,
};
use nornir::warehouse::dep_graph::{build as build_dep_graph, record_dep_graph};
use nornir::warehouse::iceberg::IcebergWarehouse;
use nornir::workspace::descriptor::WorkspaceDescriptor;
fn main() -> Result<()> {
let znippy = std::env::var("ZNIPPY_PATH")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from(format!("/run/media/{}/T9/git/znippy", std::env::var("USER").unwrap_or_default())));
let holger = std::env::var("HOLGER_PATH")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from(format!("/run/media/{}/T9/git/holger", std::env::var("USER").unwrap_or_default())));
for (name, p) in [("znippy", &znippy), ("holger", &holger)] {
anyhow::ensure!(
p.join("Cargo.toml").exists(),
"{} workspace not found at {} (set {}_PATH)",
name,
p.display(),
name.to_uppercase()
);
}
if std::env::var_os("NORNIR_MACHINE").is_none() {
let host = std::process::Command::new("uname")
.arg("-n")
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "demo-host".to_string());
// SAFETY: single-threaded section before any pipeline work spawns threads.
unsafe { std::env::set_var("NORNIR_MACHINE", &host) };
println!("🖥 NORNIR_MACHINE={host}");
}
let tmp = tempfile::tempdir().context("tempdir for descriptor")?;
let workspace_name =
std::env::var("NORNIR_WORKSPACE_NAME").unwrap_or_else(|_| "workspace_holger".to_string());
let toml = format!(
r#"[workspace]
name = "{workspace_name}"
[repos.znippy]
path = "{}"
[repos.holger]
path = "{}"
"#,
znippy.display(),
holger.display()
);
let toml_path = tmp.path().join("nornir-workspace.toml");
std::fs::write(&toml_path, toml).context("write workspace toml")?;
let desc = WorkspaceDescriptor::load(&toml_path)?;
println!(
"📄 workspace `{}` ({} repos)",
desc.workspace.name,
desc.repos.len()
);
let wh_root = std::env::var("NORNIR_WAREHOUSE")
.map(PathBuf::from)
.unwrap_or_else(|_| {
PathBuf::from(format!("/run/media/{}/T9/git/workspace_holger/.nornir/warehouse", std::env::var("USER").unwrap_or_default()))
});
if std::env::var("FRESH_WAREHOUSE").ok().as_deref() == Some("1") && wh_root.exists() {
std::fs::remove_dir_all(&wh_root).context("wipe demo warehouse")?;
}
std::fs::create_dir_all(&wh_root).context("mkdir warehouse")?;
let wh = IcebergWarehouse::open(&wh_root).context("open Iceberg warehouse")?;
println!("📦 warehouse at {}", wh_root.display());
let graph = build_dep_graph(&desc).context("build dep graph")?;
let order = graph.build_order()?;
println!(
"🔗 build order = {order:?} ({} cross-repo edges)",
graph.edges.len()
);
for e in &graph.edges {
println!(
" {} → {} via [{}]",
e.from,
e.to,
e.via.iter().cloned().collect::<Vec<_>>().join(", ")
);
}
let snapshot_id = wh
.block_on(record_dep_graph(&wh, &desc.workspace.name, &graph))
.context("record dep graph snapshot")?;
println!("📌 dep-graph snapshot = {snapshot_id}");
let opts = PipelineOptions {
dry_run: true,
dep_graph_snapshot_id: snapshot_id,
refuse_if_dirty: true,
progress: None,
gate_config: None,
};
println!("\n🚀 running pipeline (dry_run, refuse_if_dirty) — fans, here we come …");
let report: ReleaseReport = wh
.block_on(run_pipeline(&wh, &desc, &graph, &opts))
.context("run release pipeline")?;
println!("\n✅ release_id = {}", report.release_id);
for r in &report.repos {
println!(
" [{idx}] {repo:8} sha={sha} branch={br:8} dirty={dty} status={st:18} tests={p}/{f}",
idx = r.build_order_idx,
repo = r.repo,
sha = &r.git.sha[..r.git.sha.len().min(12)],
br = r.git.branch,
dty = r.git.dirty,
st = r.gate_status,
p = r.tests_passed,
f = r.tests_failed,
);
}
println!(
"\n🔎 release_lineage rows in Urðr for `{}`:",
desc.workspace.name
);
let history = wh
.block_on(query_release_history(&wh, &desc.workspace.name, None))
.context("query release history")?;
for rep in &history {
println!(" release {}", rep.release_id);
for r in &rep.repos {
println!(
" [{}] {} sha={} status={}",
r.build_order_idx,
r.repo,
&r.git.sha[..r.git.sha.len().min(12)],
r.gate_status
);
}
}
println!(
"\n ({} release(s) persisted in {})",
history.len(),
wh_root.display()
);
Ok(())
}