2026-03-31 20:02:34 -04:00
|
|
|
use std::borrow::Cow;
|
2026-04-02 18:21:00 -04:00
|
|
|
use std::time::Duration;
|
|
|
|
|
use std::time::Instant;
|
|
|
|
|
use std::time::SystemTime;
|
2026-03-31 20:02:34 -04:00
|
|
|
|
2026-02-16 20:42:03 -05:00
|
|
|
use sqlx::Row;
|
2026-02-16 20:06:46 -05:00
|
|
|
use tokio::process::Child;
|
2026-02-17 22:49:42 -05:00
|
|
|
use tracing::error;
|
2026-02-16 20:06:46 -05:00
|
|
|
|
|
|
|
|
use crate::Result;
|
|
|
|
|
use crate::database::db_handle::DbHandle;
|
2026-02-17 22:49:42 -05:00
|
|
|
use crate::nix_util::nix_output_stream::NixAction;
|
2026-02-16 21:17:25 -05:00
|
|
|
use crate::nix_util::nix_output_stream::NixOutputStream;
|
|
|
|
|
use crate::nix_util::output_stream::OutputStream;
|
2026-02-25 21:24:42 -05:00
|
|
|
|
2026-07-01 19:57:30 -04:00
|
|
|
use super::activity::Activity;
|
|
|
|
|
use super::activity_tree::ActivityTree;
|
2026-02-25 21:24:42 -05:00
|
|
|
use super::activity_tree::ActivityTreeEntry;
|
2026-06-25 18:25:02 -04:00
|
|
|
use super::activity_tree_stream::ActivityTreeStream;
|
2026-02-19 18:39:01 -05:00
|
|
|
use super::nix_output_stream::ActivityResultMessage;
|
2026-02-16 21:17:25 -05:00
|
|
|
use super::nix_output_stream::NixMessage;
|
2026-07-01 19:57:30 -04:00
|
|
|
use super::tree_iter::DrawDagEntry;
|
|
|
|
|
use super::tree_iter::ReverseTreeIter;
|
2026-02-16 20:06:46 -05:00
|
|
|
|
|
|
|
|
pub(crate) struct RunningBuild<'db> {
|
|
|
|
|
db_handle: &'db DbHandle,
|
2026-06-25 18:25:02 -04:00
|
|
|
activity_tree: ActivityTreeStream,
|
2026-04-02 18:21:00 -04:00
|
|
|
last_announce: Option<Instant>,
|
2026-02-16 20:06:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'db> RunningBuild<'db> {
|
|
|
|
|
pub(crate) fn new(db_handle: &'db DbHandle) -> Result<Self> {
|
2026-02-17 22:49:42 -05:00
|
|
|
Ok(RunningBuild {
|
|
|
|
|
db_handle,
|
2026-06-25 18:25:02 -04:00
|
|
|
activity_tree: ActivityTreeStream::new(),
|
2026-04-02 18:21:00 -04:00
|
|
|
last_announce: None,
|
2026-02-17 22:49:42 -05:00
|
|
|
})
|
2026-02-16 20:06:46 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-16 20:42:03 -05:00
|
|
|
pub(crate) async fn run_to_completion<TN>(
|
|
|
|
|
&mut self,
|
|
|
|
|
mut child: Child,
|
|
|
|
|
target_name: TN,
|
|
|
|
|
) -> Result<()>
|
|
|
|
|
where
|
|
|
|
|
TN: AsRef<str>,
|
|
|
|
|
{
|
2026-04-02 18:21:00 -04:00
|
|
|
let foo = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
|
|
|
|
|
let now = Instant::now();
|
2026-02-16 20:42:03 -05:00
|
|
|
let build_id: i64 = sqlx::query(
|
|
|
|
|
r#"INSERT INTO build (start_time, target) SELECT unixepoch('now'), ? RETURNING id"#,
|
|
|
|
|
)
|
|
|
|
|
.bind(target_name.as_ref())
|
|
|
|
|
.fetch_one(&self.db_handle.conn)
|
|
|
|
|
.await?
|
|
|
|
|
.try_get("id")?;
|
|
|
|
|
|
2026-02-16 21:17:25 -05:00
|
|
|
let output_stream = OutputStream::from_child(&mut child)?;
|
2026-02-17 22:49:42 -05:00
|
|
|
let mut nix_output_stream: NixOutputStream<OutputStream> =
|
|
|
|
|
NixOutputStream::new(output_stream);
|
2026-02-16 20:06:46 -05:00
|
|
|
|
|
|
|
|
let exit_status_handle = tokio::spawn(async move {
|
|
|
|
|
let status = child
|
|
|
|
|
.wait()
|
|
|
|
|
.await
|
|
|
|
|
.expect("nixos-rebuild encountered an error");
|
|
|
|
|
status
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-16 21:17:25 -05:00
|
|
|
while let Some(message) = nix_output_stream.next().await? {
|
|
|
|
|
self.handle_message(message)?;
|
2026-02-16 20:06:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let exit_status = exit_status_handle.await?;
|
2026-02-19 18:39:01 -05:00
|
|
|
println!("nix build status was: {}", exit_status);
|
2026-02-16 20:06:46 -05:00
|
|
|
|
2026-02-16 20:42:03 -05:00
|
|
|
let update: u64 =
|
|
|
|
|
sqlx::query(r#"UPDATE build SET end_time=unixepoch('now'), status=? WHERE id=?"#)
|
|
|
|
|
.bind(
|
|
|
|
|
exit_status
|
|
|
|
|
.code()
|
|
|
|
|
.expect("Process should have an exit code."),
|
|
|
|
|
)
|
|
|
|
|
.bind(build_id)
|
|
|
|
|
.execute(&self.db_handle.conn)
|
|
|
|
|
.await?
|
|
|
|
|
.rows_affected();
|
|
|
|
|
assert!(update == 1);
|
|
|
|
|
|
2026-02-16 20:06:46 -05:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 22:49:42 -05:00
|
|
|
pub(crate) fn handle_message(&mut self, message: NixMessage) -> Result<()> {
|
2026-06-25 18:25:02 -04:00
|
|
|
self.activity_tree.handle_message(&message)?;
|
|
|
|
|
|
2026-02-17 22:49:42 -05:00
|
|
|
let message = match message {
|
|
|
|
|
NixMessage::ParseFailure(line) => {
|
|
|
|
|
error!("FAIL PARSE: {line}");
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
NixMessage::Generic(_value, line) => {
|
|
|
|
|
error!("GENERIC PARSE: {line}");
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
NixMessage::Action(nix_action) => nix_action,
|
|
|
|
|
};
|
2026-02-19 18:39:01 -05:00
|
|
|
match message {
|
|
|
|
|
NixAction::Msg(msg_message) => {
|
2026-06-25 18:25:02 -04:00
|
|
|
if msg_message.level > 0 && msg_message.level < 5 {
|
|
|
|
|
eprintln!("LOG MESSAGE {}: {}", msg_message.level, msg_message.msg);
|
2026-03-01 18:02:58 -05:00
|
|
|
}
|
2026-02-17 22:49:42 -05:00
|
|
|
}
|
2026-06-25 18:25:02 -04:00
|
|
|
NixAction::Start(_activity_start_message) => {
|
2026-03-01 18:02:58 -05:00
|
|
|
self.print_current_status();
|
2026-02-17 22:49:42 -05:00
|
|
|
}
|
2026-06-25 18:25:02 -04:00
|
|
|
NixAction::Stop(_stop_message) => {
|
2026-03-01 18:02:58 -05:00
|
|
|
self.print_current_status();
|
2026-02-17 22:49:42 -05:00
|
|
|
// println!("{}", serde_json::to_string(&message)?);
|
|
|
|
|
}
|
2026-02-19 18:39:01 -05:00
|
|
|
NixAction::Result(activity_result_message) => {
|
|
|
|
|
match activity_result_message {
|
2026-02-28 14:53:43 -05:00
|
|
|
ActivityResultMessage::FileLinked(_activity_result_file_linked) => {}
|
|
|
|
|
ActivityResultMessage::BuildLogLine(_activity_result_build_log_line) => {
|
|
|
|
|
// These are the output from the actual build (as opposed to the output from nix).
|
2026-02-17 22:49:42 -05:00
|
|
|
}
|
2026-02-28 14:53:43 -05:00
|
|
|
ActivityResultMessage::UntrustedPath(_activity_result_untrusted_path) => {}
|
|
|
|
|
ActivityResultMessage::CorruptedPath(_activity_result_corrupted_path) => {}
|
2026-06-25 18:25:02 -04:00
|
|
|
ActivityResultMessage::SetPhase(_activity_result_set_phase) => {
|
2026-03-31 20:02:34 -04:00
|
|
|
self.print_current_status();
|
2026-02-17 22:49:42 -05:00
|
|
|
}
|
2026-07-18 11:22:48 -04:00
|
|
|
ActivityResultMessage::Progress(activity_result_progress) => {
|
|
|
|
|
if activity_result_progress.done == activity_result_progress.expected
|
|
|
|
|
&& activity_result_progress.expected > 0
|
|
|
|
|
{
|
|
|
|
|
// If we finished the in-progress activity, always render so we show the 100% done version.
|
|
|
|
|
self.print_current_status();
|
|
|
|
|
} else {
|
|
|
|
|
self.maybe_print_current_status();
|
|
|
|
|
}
|
2026-02-17 22:49:42 -05:00
|
|
|
}
|
2026-06-25 18:25:02 -04:00
|
|
|
ActivityResultMessage::SetExpected(_activity_result_set_expected) => {
|
2026-04-02 18:21:00 -04:00
|
|
|
self.maybe_print_current_status();
|
2026-02-17 22:49:42 -05:00
|
|
|
}
|
2026-02-19 18:39:01 -05:00
|
|
|
ActivityResultMessage::PostBuildLogLine(
|
2026-02-28 14:53:43 -05:00
|
|
|
_activity_result_post_build_log_line,
|
|
|
|
|
) => {}
|
|
|
|
|
ActivityResultMessage::FetchStatus(_activity_result_fetch_status) => {}
|
2026-02-17 22:49:42 -05:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-02-16 20:06:46 -05:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-17 22:49:42 -05:00
|
|
|
|
2026-04-02 18:21:00 -04:00
|
|
|
fn maybe_print_current_status(&mut self) -> () {
|
|
|
|
|
let last_announce = match self.last_announce {
|
|
|
|
|
Some(instant) => instant,
|
|
|
|
|
None => {
|
|
|
|
|
// If we haven't announced before, always announce.
|
|
|
|
|
return self.print_current_status();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let now = Instant::now();
|
|
|
|
|
let time_since_last_announce = now.duration_since(last_announce);
|
|
|
|
|
if time_since_last_announce > Duration::new(5, 0) {
|
|
|
|
|
return self.print_current_status();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn print_current_status(&mut self) -> () {
|
2026-07-01 19:57:30 -04:00
|
|
|
// let draw_order = get_draw_order(self.activity_tree.get_tree());
|
|
|
|
|
let draw_order = ReverseTreeIter::new(
|
|
|
|
|
self.activity_tree.get_tree(),
|
|
|
|
|
None,
|
|
|
|
|
is_match_predicate,
|
|
|
|
|
is_transparent_predicate,
|
|
|
|
|
is_alive_predicate,
|
|
|
|
|
)
|
|
|
|
|
.get_draw_order();
|
|
|
|
|
|
|
|
|
|
print_dag(self.activity_tree.get_tree(), draw_order);
|
2026-04-02 18:21:00 -04:00
|
|
|
self.last_announce = Some(Instant::now());
|
2026-02-21 21:50:08 -05:00
|
|
|
}
|
2026-07-01 19:57:30 -04:00
|
|
|
}
|
2026-02-21 21:50:08 -05:00
|
|
|
|
2026-07-01 19:57:30 -04:00
|
|
|
pub(crate) fn print_dag(activity_tree: &ActivityTree, draw_order: Vec<DrawDagEntry>) -> () {
|
|
|
|
|
let mut tree = String::new();
|
|
|
|
|
for dag_entry in draw_order {
|
|
|
|
|
let leading_bars = {
|
|
|
|
|
let mut leading_bars = String::with_capacity(3 * dag_entry.depth.len());
|
|
|
|
|
for leading_bar in dag_entry
|
|
|
|
|
.depth
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|depth| if *depth { " " } else { " " })
|
|
|
|
|
{
|
|
|
|
|
leading_bars.push_str(leading_bar);
|
|
|
|
|
}
|
|
|
|
|
leading_bars
|
|
|
|
|
};
|
|
|
|
|
let branch = if dag_entry.has_later_siblings {
|
|
|
|
|
""
|
|
|
|
|
} else {
|
|
|
|
|
""
|
|
|
|
|
};
|
|
|
|
|
let activity = activity_tree.get(&dag_entry.activity_id);
|
|
|
|
|
let display_name = activity
|
|
|
|
|
.get_activity()
|
|
|
|
|
.display_name()
|
|
|
|
|
.expect("Currently we always return a display name.");
|
|
|
|
|
let progress_text = activity.get_activity().get_progress_text();
|
|
|
|
|
let (progress, progress_sep) = match progress_text {
|
|
|
|
|
Some(text) => (text, " "),
|
|
|
|
|
None => (Cow::Borrowed(""), ""),
|
|
|
|
|
};
|
|
|
|
|
tree += &format!("{leading_bars}{branch} {progress}{progress_sep}{display_name}\n");
|
2026-02-21 21:50:08 -05:00
|
|
|
}
|
2026-07-01 19:57:30 -04:00
|
|
|
if tree.is_empty() {
|
|
|
|
|
println!("No active activities.");
|
|
|
|
|
} else {
|
|
|
|
|
print!("\n{}\n", tree);
|
2026-02-17 22:49:42 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-01 19:57:30 -04:00
|
|
|
fn is_match_predicate(_entry: &ActivityTreeEntry) -> bool {
|
|
|
|
|
true
|
2026-02-21 21:50:08 -05:00
|
|
|
}
|
|
|
|
|
|
2026-07-01 19:57:30 -04:00
|
|
|
pub(crate) fn is_transparent_predicate(entry: &ActivityTreeEntry) -> bool {
|
|
|
|
|
match entry.get_activity() {
|
|
|
|
|
Activity::Root(_activity_root) => true,
|
|
|
|
|
Activity::Unknown(_activity_unknown) => false,
|
|
|
|
|
Activity::CopyPath(_activity_copy_path) => false,
|
|
|
|
|
Activity::FileTransfer(_activity_file_transfer) => false,
|
|
|
|
|
Activity::Realize(_activity_realize) => true,
|
|
|
|
|
Activity::CopyPaths(_activity_copy_paths) => true,
|
|
|
|
|
Activity::Builds(_activity_builds) => true,
|
|
|
|
|
Activity::Build(_activity_build) => false,
|
|
|
|
|
Activity::OptimizeStore(_activity_optimize_store) => false,
|
|
|
|
|
Activity::VerifyPaths(_activity_verify_paths) => false,
|
|
|
|
|
Activity::Substitute(_activity_substitute) => false,
|
|
|
|
|
Activity::QueryPathInfo(_activity_query_path_info) => false,
|
|
|
|
|
Activity::PostBuildHook(_activity_post_build_hook) => false,
|
|
|
|
|
Activity::BuildWaiting(_activity_build_waiting) => true,
|
|
|
|
|
Activity::FetchTree(_activity_fetch_tree) => false,
|
|
|
|
|
}
|
2026-02-21 21:50:08 -05:00
|
|
|
}
|
|
|
|
|
|
2026-07-01 19:57:30 -04:00
|
|
|
fn is_alive_predicate(entry: &ActivityTreeEntry) -> bool {
|
|
|
|
|
entry.get_activity().is_active()
|
2026-02-21 21:50:08 -05:00
|
|
|
}
|