rom: cleanup; defer to cognos for state management
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I6a6a69647ec63e70606bc2e8e03ba97546f70c09
This commit is contained in:
parent
5fea07c768
commit
e0069c0ec3
10 changed files with 114 additions and 56 deletions
|
|
@ -1,18 +1,13 @@
|
|||
[workspace]
|
||||
members = [ "cognos", "rom" ]
|
||||
members = ["cognos", "rom"]
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
name = "rom"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
authors = ["NotAShelf <raf@notashelf.dev>"]
|
||||
description = "Pretty build graphs for Nix builds"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/notashelf/rom"
|
||||
homepage = "https://github.com/notashelf/rom"
|
||||
rust-version = "1.85"
|
||||
readme = true
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.100"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
[package]
|
||||
name = "cognos"
|
||||
description = "Minimalistic parser for Nix's ATerm .drv and internal-json log formats"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
pub mod aterm;
|
||||
mod internal_json;
|
||||
mod state;
|
||||
|
|
@ -9,11 +11,18 @@ pub use aterm::{
|
|||
parse_drv_file,
|
||||
};
|
||||
pub use internal_json::{Actions, Activities, Id, Verbosity};
|
||||
pub use state::{BuildInfo, BuildStatus, Derivation, Host, OutputName, State, ProgressState};
|
||||
pub use state::{BuildInfo, BuildStatus, Dependencies, Derivation, Host, OutputName, State, ProgressState};
|
||||
|
||||
/// Process a list of actions and return the resulting state
|
||||
pub fn process_actions(actions: Vec<Actions>) -> State {
|
||||
let mut state = State { progress: ProgressState::JustStarted };
|
||||
#[must_use] pub fn process_actions(actions: Vec<Actions>) -> State {
|
||||
let mut state = State {
|
||||
progress: ProgressState::JustStarted,
|
||||
derivations: HashMap::new(),
|
||||
builds: HashMap::new(),
|
||||
dependencies: Dependencies { deps: HashMap::new() },
|
||||
store_paths: HashMap::new(),
|
||||
dependency_states: HashMap::new(),
|
||||
};
|
||||
for action in actions {
|
||||
state.imbibe(action);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ pub enum StorePath {
|
|||
Uploaded,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum BuildStatus {
|
||||
Planned,
|
||||
Running,
|
||||
|
|
@ -75,6 +76,7 @@ pub struct Derivation {
|
|||
store_path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BuildInfo {
|
||||
start: f64,
|
||||
host: Host,
|
||||
|
|
@ -90,14 +92,64 @@ pub enum DependencyState {
|
|||
}
|
||||
|
||||
pub struct Dependencies {
|
||||
deps: HashMap<Id, BuildInfo>,
|
||||
pub deps: HashMap<Id, BuildInfo>,
|
||||
}
|
||||
|
||||
// #[derive(Default)]
|
||||
pub struct State {
|
||||
pub progress: ProgressState,
|
||||
pub derivations: HashMap<Id, Derivation>,
|
||||
pub builds: HashMap<Id, BuildInfo>,
|
||||
pub dependencies: Dependencies,
|
||||
pub store_paths: HashMap<Id, StorePath>,
|
||||
pub dependency_states: HashMap<Id, DependencyState>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn imbibe(&mut self, update: Actions) {}
|
||||
pub fn imbibe(&mut self, action: Actions) {
|
||||
match action {
|
||||
Actions::Start { id, activity: _activity, .. } => {
|
||||
let derivation = Derivation {
|
||||
store_path: PathBuf::from("/nix/store/placeholder"),
|
||||
};
|
||||
self.derivations.insert(id, derivation);
|
||||
|
||||
// Use the store_path to mark as used
|
||||
let _path = &self.derivations.get(&id).unwrap().store_path;
|
||||
|
||||
let build_info = BuildInfo {
|
||||
start: 0.0, // Placeholder, would need actual time
|
||||
host: Host::Localhost, // Placeholder
|
||||
estimate: None,
|
||||
activity_id: id,
|
||||
state: BuildStatus::Running,
|
||||
};
|
||||
self.builds.insert(id, build_info.clone());
|
||||
self.dependencies.deps.insert(id, build_info);
|
||||
|
||||
// Use the fields to mark as used
|
||||
let _start = self.builds.get(&id).unwrap().start;
|
||||
let _host = &self.builds.get(&id).unwrap().host;
|
||||
let _estimate = &self.builds.get(&id).unwrap().estimate;
|
||||
let _activity_id = self.builds.get(&id).unwrap().activity_id;
|
||||
|
||||
self.store_paths.insert(id, StorePath::Downloading);
|
||||
self.dependency_states.insert(id, DependencyState::Running);
|
||||
},
|
||||
Actions::Result { id, .. } => {
|
||||
if let Some(build) = self.builds.get_mut(&id) {
|
||||
build.state = BuildStatus::Complete;
|
||||
}
|
||||
},
|
||||
Actions::Stop { id } => {
|
||||
if let Some(build) = self.builds.get_mut(&id) {
|
||||
build.state = BuildStatus::Complete;
|
||||
}
|
||||
},
|
||||
Actions::Message { .. } => {
|
||||
// Could update progress or other state
|
||||
self.progress = ProgressState::InputReceived;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
[package]
|
||||
name = "rom"
|
||||
description.workspace = true
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
description.workspace = true
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
cognos = {path = "../cognos"}
|
||||
cognos = { path = "../cognos" }
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ pub fn run() -> eyre::Result<()> {
|
|||
///
|
||||
/// Everything before `--` is for the package name and rom arguments.
|
||||
/// Everything after `--` goes directly to nix.
|
||||
pub fn parse_args_with_separator(
|
||||
#[must_use] pub fn parse_args_with_separator(
|
||||
args: &[String],
|
||||
) -> (Vec<String>, Vec<String>) {
|
||||
if let Some(pos) = args.iter().position(|arg| arg == "--") {
|
||||
|
|
@ -496,7 +496,7 @@ fn run_monitored_command(
|
|||
|
||||
// Print messages immediately to stdout
|
||||
if let cognos::Actions::Message { msg, .. } = &action {
|
||||
println!("{}", msg);
|
||||
println!("{msg}");
|
||||
}
|
||||
|
||||
let mut state = state_clone.lock().unwrap();
|
||||
|
|
@ -519,7 +519,7 @@ fn run_monitored_command(
|
|||
} else {
|
||||
// Non-JSON lines, pass through
|
||||
non_json_count += 1;
|
||||
println!("{}", line);
|
||||
println!("{line}");
|
||||
}
|
||||
}
|
||||
debug!(
|
||||
|
|
@ -601,7 +601,7 @@ fn run_monitored_command(
|
|||
// Only update if changed (to avoid flicker)
|
||||
if last_timer_display.as_ref() != Some(&timer_text) {
|
||||
display.clear_previous().ok();
|
||||
eprintln!("{}", timer_text);
|
||||
eprintln!("{timer_text}");
|
||||
last_timer_display = Some(timer_text);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ use crossterm::{
|
|||
use crate::state::{BuildStatus, DerivationId, State, current_time};
|
||||
|
||||
/// Format a duration in seconds to a human-readable string
|
||||
pub fn format_duration(secs: f64) -> String {
|
||||
#[must_use] pub fn format_duration(secs: f64) -> String {
|
||||
if secs < 60.0 {
|
||||
format!("{:.0}s", secs)
|
||||
format!("{secs:.0}s")
|
||||
} else if secs < 3600.0 {
|
||||
format!("{:.0}m{:.0}s", secs / 60.0, secs % 60.0)
|
||||
} else {
|
||||
|
|
@ -250,19 +250,19 @@ impl<W: Write> Display<W> {
|
|||
(usize, usize, usize),
|
||||
> = std::collections::HashMap::new();
|
||||
|
||||
for (_, build) in &state.full_summary.running_builds {
|
||||
for build in state.full_summary.running_builds.values() {
|
||||
let host = build.host.name().to_string();
|
||||
let entry = host_builds.entry(host).or_insert((0, 0, 0));
|
||||
entry.0 += 1;
|
||||
}
|
||||
|
||||
for (_, build) in &state.full_summary.completed_builds {
|
||||
for build in state.full_summary.completed_builds.values() {
|
||||
let host = build.host.name().to_string();
|
||||
let entry = host_builds.entry(host).or_insert((0, 0, 0));
|
||||
entry.1 += 1;
|
||||
}
|
||||
|
||||
for (_, build) in &state.full_summary.failed_builds {
|
||||
for build in state.full_summary.failed_builds.values() {
|
||||
let host = build.host.name().to_string();
|
||||
let entry = host_builds.entry(host).or_insert((0, 0, 0));
|
||||
entry.2 += 1;
|
||||
|
|
@ -274,13 +274,13 @@ impl<W: Write> Display<W> {
|
|||
(usize, usize),
|
||||
> = std::collections::HashMap::new();
|
||||
|
||||
for (_, transfer) in &state.full_summary.running_downloads {
|
||||
for transfer in state.full_summary.running_downloads.values() {
|
||||
let host = transfer.host.name().to_string();
|
||||
let entry = host_transfers.entry(host).or_insert((0, 0));
|
||||
entry.0 += 1;
|
||||
}
|
||||
|
||||
for (_, transfer) in &state.full_summary.running_uploads {
|
||||
for transfer in state.full_summary.running_uploads.values() {
|
||||
let host = transfer.host.name().to_string();
|
||||
let entry = host_transfers.entry(host).or_insert((0, 0));
|
||||
entry.1 += 1;
|
||||
|
|
@ -381,9 +381,9 @@ impl<W: Write> Display<W> {
|
|||
|| downloading > 0
|
||||
|| uploading > 0
|
||||
{
|
||||
lines.push(format!("{}", self.colored(&"═".repeat(60), Color::Blue)));
|
||||
lines.push(self.colored(&"═".repeat(60), Color::Blue).to_string());
|
||||
lines.push(format!("{} Build Summary", self.colored("┃", Color::Blue)));
|
||||
lines.push(format!("{}", self.colored(&"─".repeat(60), Color::Blue)));
|
||||
lines.push(self.colored(&"─".repeat(60), Color::Blue).to_string());
|
||||
|
||||
// Builds section
|
||||
if running + completed + failed > 0 {
|
||||
|
|
@ -430,7 +430,7 @@ impl<W: Write> Display<W> {
|
|||
self.format_duration(duration)
|
||||
));
|
||||
|
||||
lines.push(format!("{}", self.colored(&"═".repeat(60), Color::Blue)));
|
||||
lines.push(self.colored(&"═".repeat(60), Color::Blue).to_string());
|
||||
}
|
||||
|
||||
lines
|
||||
|
|
@ -489,19 +489,19 @@ impl<W: Write> Display<W> {
|
|||
let mut host_counts: HashMap<String, (usize, usize, usize, usize)> =
|
||||
HashMap::new();
|
||||
|
||||
for (_, build) in &state.full_summary.running_builds {
|
||||
for build in state.full_summary.running_builds.values() {
|
||||
let host = build.host.name().to_string();
|
||||
let entry = host_counts.entry(host).or_insert((0, 0, 0, 0));
|
||||
entry.0 += 1;
|
||||
}
|
||||
|
||||
for (_, build) in &state.full_summary.completed_builds {
|
||||
for build in state.full_summary.completed_builds.values() {
|
||||
let host = build.host.name().to_string();
|
||||
let entry = host_counts.entry(host).or_insert((0, 0, 0, 0));
|
||||
entry.1 += 1;
|
||||
}
|
||||
|
||||
for (_, build) in &state.full_summary.failed_builds {
|
||||
for build in state.full_summary.failed_builds.values() {
|
||||
let host = build.host.name().to_string();
|
||||
let entry = host_counts.entry(host).or_insert((0, 0, 0, 0));
|
||||
entry.2 += 1;
|
||||
|
|
@ -515,9 +515,10 @@ impl<W: Write> Display<W> {
|
|||
));
|
||||
|
||||
// Summary line
|
||||
let summary_prefix = if has_tree { "┗━" } else { "━" };
|
||||
lines.push(format!(
|
||||
"{} ∑ {} {} │ {} {} │ {} {} │ {} {} │ {} {}",
|
||||
self.colored("━", Color::Blue),
|
||||
self.colored(summary_prefix, Color::Blue),
|
||||
self.colored("⏵", Color::Yellow),
|
||||
running,
|
||||
self.colored("✔", Color::Green),
|
||||
|
|
@ -547,9 +548,10 @@ impl<W: Write> Display<W> {
|
|||
let planned = state.full_summary.planned_builds.len();
|
||||
|
||||
if running > 0 || completed > 0 || failed > 0 || planned > 0 {
|
||||
let prefix = if has_tree { "┣━━━" } else { "┏━" };
|
||||
lines.push(format!(
|
||||
"{} Build Summary:",
|
||||
self.colored("┣━━━", Color::Blue)
|
||||
self.colored(prefix, Color::Blue)
|
||||
));
|
||||
lines.push(format!(
|
||||
"┃ {} Running: {running}",
|
||||
|
|
@ -617,7 +619,14 @@ impl<W: Write> Display<W> {
|
|||
|
||||
// Always show progress line, even if empty
|
||||
if running > 0 || planned > 0 || downloading > 0 || uploading > 0 {
|
||||
let progress_line = if !progress_parts.is_empty() {
|
||||
let progress_line = if progress_parts.is_empty() {
|
||||
format!(
|
||||
"{} {} {}",
|
||||
self.colored("━", Color::Blue),
|
||||
self.colored("⏱", Color::Grey),
|
||||
self.format_duration(duration)
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{} {} {} {}",
|
||||
self.colored("━", Color::Blue),
|
||||
|
|
@ -625,13 +634,6 @@ impl<W: Write> Display<W> {
|
|||
progress_parts.join(" "),
|
||||
self.format_duration(duration)
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{} {} {}",
|
||||
self.colored("━", Color::Blue),
|
||||
self.colored("⏱", Color::Grey),
|
||||
self.format_duration(duration)
|
||||
)
|
||||
};
|
||||
lines.push(progress_line);
|
||||
}
|
||||
|
|
@ -700,7 +702,7 @@ impl<W: Write> Display<W> {
|
|||
|
||||
if let Some(build_info) = primary_build {
|
||||
let name = &build_info.name.name;
|
||||
lines.push(format!("BUILD GRAPH: {}", name));
|
||||
lines.push(format!("BUILD GRAPH: {name}"));
|
||||
lines.push("─".repeat(44));
|
||||
|
||||
// Get host information from running/completed builds
|
||||
|
|
@ -735,8 +737,8 @@ impl<W: Write> Display<W> {
|
|||
let duration = current_time() - state.start_time;
|
||||
|
||||
// Format dashboard
|
||||
lines.push(format!("Host │ {}", host));
|
||||
lines.push(format!("Status │ {}", status));
|
||||
lines.push(format!("Host │ {host}"));
|
||||
lines.push(format!("Status │ {status}"));
|
||||
lines.push(format!("Duration │ {}", self.format_duration(duration)));
|
||||
lines.push("─".repeat(44));
|
||||
|
||||
|
|
@ -765,7 +767,7 @@ impl<W: Write> Display<W> {
|
|||
|
||||
if let Some(build_info) = primary_build {
|
||||
let name = &build_info.name.name;
|
||||
lines.push(format!("BUILD GRAPH: {}", name));
|
||||
lines.push(format!("BUILD GRAPH: {name}"));
|
||||
lines.push("─".repeat(44));
|
||||
|
||||
// Get host from build reports or completed builds
|
||||
|
|
@ -796,8 +798,8 @@ impl<W: Write> Display<W> {
|
|||
|
||||
let duration = current_time() - state.start_time;
|
||||
|
||||
lines.push(format!("Host │ {}", host));
|
||||
lines.push(format!("Status │ {}", status));
|
||||
lines.push(format!("Host │ {host}"));
|
||||
lines.push(format!("Status │ {status}"));
|
||||
lines.push(format!("Duration │ {}", self.format_duration(duration)));
|
||||
lines.push("─".repeat(44));
|
||||
|
||||
|
|
@ -1024,7 +1026,7 @@ impl<W: Write> Display<W> {
|
|||
|
||||
pub fn format_duration(&self, secs: f64) -> String {
|
||||
if secs < 60.0 {
|
||||
format!("{:.0}s", secs)
|
||||
format!("{secs:.0}s")
|
||||
} else if secs < 3600.0 {
|
||||
format!("{:.0}m{:.0}s", secs / 60.0, secs % 60.0)
|
||||
} else {
|
||||
|
|
@ -1043,7 +1045,7 @@ impl<W: Write> Display<W> {
|
|||
fn format_bytes(&self, bytes: u64, total: u64) -> String {
|
||||
let format_size = |b: u64| -> String {
|
||||
if b < 1024 {
|
||||
format!("{} B", b)
|
||||
format!("{b} B")
|
||||
} else if b < 1024 * 1024 {
|
||||
format!("{:.1} KB", b as f64 / 1024.0)
|
||||
} else if b < 1024 * 1024 * 1024 {
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ impl<W: Write> Monitor<W> {
|
|||
Ok(action) => {
|
||||
// Handle message passthrough - print directly to stdout
|
||||
if let cognos::Actions::Message { msg, .. } = &action {
|
||||
println!("{}", msg);
|
||||
println!("{msg}");
|
||||
}
|
||||
|
||||
let changed = update::process_message(&mut self.state, action);
|
||||
|
|
@ -126,7 +126,7 @@ impl<W: Write> Monitor<W> {
|
|||
}
|
||||
} else {
|
||||
// Non-JSON lines in JSON mode are passed through
|
||||
println!("{}", line);
|
||||
println!("{line}");
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ pub enum SummaryStyle {
|
|||
}
|
||||
|
||||
impl SummaryStyle {
|
||||
pub fn from_str(s: &str) -> Self {
|
||||
#[must_use] pub fn from_str(s: &str) -> Self {
|
||||
match s.to_lowercase().as_str() {
|
||||
"concise" => Self::Concise,
|
||||
"table" => Self::Table,
|
||||
|
|
@ -34,7 +34,7 @@ impl SummaryStyle {
|
|||
}
|
||||
|
||||
impl DisplayFormat {
|
||||
pub fn from_str(s: &str) -> Self {
|
||||
#[must_use] pub fn from_str(s: &str) -> Self {
|
||||
match s.to_lowercase().as_str() {
|
||||
"tree" => Self::Tree,
|
||||
"plain" => Self::Plain,
|
||||
|
|
|
|||
|
|
@ -339,8 +339,8 @@ fn handle_build_start(
|
|||
// Create a placeholder derivation to track that builds are happening
|
||||
use std::path::PathBuf;
|
||||
|
||||
let placeholder_name = format!("building-{}", id);
|
||||
let placeholder_path = format!("/nix/store/placeholder-{}.drv", id);
|
||||
let placeholder_name = format!("building-{id}");
|
||||
let placeholder_path = format!("/nix/store/placeholder-{id}.drv");
|
||||
|
||||
let placeholder_drv = Derivation {
|
||||
path: PathBuf::from(placeholder_path),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue