use std::borrow::Cow; use std::collections::BTreeMap; use std::collections::BTreeSet; use sqlx::Row; use tokio::process::Child; use tracing::error; use tracing::warn; use crate::Result; use crate::database::db_handle::DbHandle; use crate::nix_util::nix_output_stream::ActivityStartMessage; use crate::nix_util::nix_output_stream::NixAction; use crate::nix_util::nix_output_stream::NixOutputStream; use crate::nix_util::output_stream::OutputStream; use super::nix_output_stream::ActivityResultMessage; use super::nix_output_stream::NixMessage; pub(crate) struct RunningBuild<'db> { db_handle: &'db DbHandle, activity_tree: BTreeMap, } impl<'db> RunningBuild<'db> { pub(crate) fn new(db_handle: &'db DbHandle) -> Result { Ok(RunningBuild { db_handle, activity_tree: BTreeMap::new(), }) } pub(crate) async fn run_to_completion( &mut self, mut child: Child, target_name: TN, ) -> Result<()> where TN: AsRef, { 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")?; let output_stream = OutputStream::from_child(&mut child)?; let mut nix_output_stream: NixOutputStream = NixOutputStream::new(output_stream); let exit_status_handle = tokio::spawn(async move { let status = child .wait() .await .expect("nixos-rebuild encountered an error"); status }); while let Some(message) = nix_output_stream.next().await? { self.handle_message(message)?; } let exit_status = exit_status_handle.await?; println!("nix build status was: {}", exit_status); 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); Ok(()) } pub(crate) fn handle_message(&mut self, message: NixMessage) -> Result<()> { 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, }; match message { NixAction::Msg(msg_message) => { // For now we can ignore the messages. } NixAction::Start(activity_start_message) => { let id = activity_start_message.get_id(); let entry = self.activity_tree.entry(id); let entry = match entry { std::collections::btree_map::Entry::Vacant(vacant_entry) => vacant_entry, std::collections::btree_map::Entry::Occupied(_occupied_entry) => { panic!("Started an already started activity: {id}.") } }; match activity_start_message { ActivityStartMessage::Unknown(activity_start_unknown) => { entry.insert(Activity::Unknown(ActivityUnknown { id: activity_start_unknown.id, parent: activity_start_unknown.parent, state: ActivityState::default(), })); } ActivityStartMessage::CopyPath(activity_start_copy_path) => { entry.insert(Activity::CopyPath(ActivityCopyPath { id: activity_start_copy_path.id, parent: activity_start_copy_path.parent, state: ActivityState::default(), })); } ActivityStartMessage::FileTransfer(activity_start_file_transfer) => { entry.insert(Activity::FileTransfer(ActivityFileTransfer { id: activity_start_file_transfer.id, parent: activity_start_file_transfer.parent, state: ActivityState::default(), url: activity_start_file_transfer.url, })); } ActivityStartMessage::Realize(activity_start_realize) => { entry.insert(Activity::Realize(ActivityRealize { id: activity_start_realize.id, parent: activity_start_realize.parent, state: ActivityState::default(), })); } ActivityStartMessage::CopyPaths(activity_start_copy_paths) => { entry.insert(Activity::CopyPaths(ActivityCopyPaths { id: activity_start_copy_paths.id, parent: activity_start_copy_paths.parent, state: ActivityState::default(), })); } ActivityStartMessage::Builds(activity_start_builds) => { entry.insert(Activity::Builds(ActivityBuilds { id: activity_start_builds.id, parent: activity_start_builds.parent, state: ActivityState::default(), })); } ActivityStartMessage::Build(activity_start_build) => { entry.insert(Activity::Build(ActivityBuild { id: activity_start_build.id, parent: activity_start_build.parent, state: ActivityState::default(), drv_path: activity_start_build.drv_path, machine_name: if activity_start_build.machine_name.len() > 0 { Some(activity_start_build.machine_name) } else { None }, })); } ActivityStartMessage::OptimizeStore(activity_start_optimize_store) => { entry.insert(Activity::OptimizeStore(ActivityOptimizeStore { id: activity_start_optimize_store.id, parent: activity_start_optimize_store.parent, state: ActivityState::default(), })); } ActivityStartMessage::VerifyPaths(activity_start_verify_paths) => { entry.insert(Activity::VerifyPaths(ActivityVerifyPaths { id: activity_start_verify_paths.id, parent: activity_start_verify_paths.parent, state: ActivityState::default(), })); } ActivityStartMessage::Substitute(activity_start_substitute) => { entry.insert(Activity::Substitute(ActivitySubstitute { id: activity_start_substitute.id, parent: activity_start_substitute.parent, state: ActivityState::default(), })); } ActivityStartMessage::QueryPathInfo(activity_start_query_path_info) => { entry.insert(Activity::QueryPathInfo(ActivityQueryPathInfo { id: activity_start_query_path_info.id, parent: activity_start_query_path_info.parent, state: ActivityState::default(), })); } ActivityStartMessage::PostBuildHook(activity_start_post_build_hook) => { entry.insert(Activity::PostBuildHook(ActivityPostBuildHook { id: activity_start_post_build_hook.id, parent: activity_start_post_build_hook.parent, state: ActivityState::default(), })); } ActivityStartMessage::BuildWaiting(activity_start_build_waiting) => { entry.insert(Activity::BuildWaiting(ActivityBuildWaiting { id: activity_start_build_waiting.id, state: ActivityState::default(), })); } ActivityStartMessage::FetchTree(activity_start_fetch_tree) => { entry.insert(Activity::FetchTree(ActivityFetchTree { id: activity_start_fetch_tree.id, parent: activity_start_fetch_tree.parent, state: ActivityState::default(), })); } }; self.print_current_status(); } NixAction::Stop(stop_message) => { let entry = self.activity_tree.entry(stop_message.id); match entry { std::collections::btree_map::Entry::Vacant(_vacant_entry) => { panic!( "Stopped an activity that is not in the tree: {}", stop_message.id ); } std::collections::btree_map::Entry::Occupied(mut occupied_entry) => { occupied_entry.get_mut().stop(); } }; self.print_current_status(); // println!("{}", serde_json::to_string(&message)?); } NixAction::Result(activity_result_message) => { match activity_result_message { ActivityResultMessage::FileLinked(activity_result_file_linked) => { // FileLinked // TODO: Haven't seen any of these. // warn!("Found FileLinked: {}", serde_json::to_string(&message)?); } ActivityResultMessage::BuildLogLine(activity_result_build_log_line) => { // BuildLogLine // The first field is a string containing the log line } ActivityResultMessage::UntrustedPath(activity_result_untrusted_path) => { // UntrustedPath // TODO: Haven't seen any of these. // warn!("Found UntrustedPath: {}", serde_json::to_string(&message)?); } ActivityResultMessage::CorruptedPath(activity_result_corrupted_path) => { // CorruptedPath // TODO: Haven't seen any of these. // warn!("Found CorruptedPath: {}", serde_json::to_string(&message)?); } ActivityResultMessage::SetPhase(activity_result_set_phase) => { // SetPhase // The first field is the phase name } ActivityResultMessage::Progress(activity_result_progress) => { // Progress // Fields numerator, denominator, running?, failed? } ActivityResultMessage::SetExpected(activity_result_set_expected) => { // SetExpected // Fields activity type?, expected? } ActivityResultMessage::PostBuildLogLine( activity_result_post_build_log_line, ) => { // PostBuildLogLine // TODO: Haven't seen any of these. // warn!( // "Found PostBuildLogLine: {}", // serde_json::to_string(&message)? // ); } ActivityResultMessage::FetchStatus(activity_result_fetch_status) => { // FetchStatus // TODO: Haven't seen any of these. // warn!("Found FetchStatus: {}", serde_json::to_string(&message)?); // println!("{}", serde_json::to_string(&message)?); } }; } }; Ok(()) } fn print_current_status(&self) -> () { let mut tree = String::new(); let draw_order = self.get_draw_order(); 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 = self .activity_tree .get(&dag_entry.activity_id) .expect("Activity should exist in the tree."); let display_name = activity .display_name() .expect("Currently we always return a display name."); let activity_id = activity.get_activity_id(); let parent_id = activity.get_parent_id(); tree += &format!("{leading_bars}{branch}𜸟 {display_name} {parent_id} {activity_id}\n"); } if tree.is_empty() { println!("No active activities."); } else { print!("\n{}\n", tree); } } fn get_draw_order(&self) -> Vec { let active_activity_ids: BTreeSet = self .activity_tree .iter() .filter(|(activity_id, activity)| activity.is_active()) .flat_map(|(activity_id, activity)| { PathToRootIter::new(&self.activity_tree, *activity_id) }) .collect(); let mut draw_order: Vec = Vec::new(); let mut stack: Vec = self .get_children_in_order(0) .filter_map(|child_id| { if active_activity_ids.contains(&child_id) { Some(DrawStackEntry::HasNotVisitedChildren(DrawDagEntry { activity_id: child_id, depth: Vec::new(), has_later_siblings: true, })) } else { None } }) .collect(); if stack.len() == 0 { return draw_order; } stack .last_mut() .expect("If-statement ensured this is Some()") .set_no_later_siblings(); while !stack.is_empty() { let current_entry = stack.pop().expect("While-loop ensured this is Some."); match current_entry { DrawStackEntry::HasNotVisitedChildren(draw_dag_entry) => { let current_id = draw_dag_entry.activity_id; let mut current_depth = draw_dag_entry.depth.clone(); current_depth.push(draw_dag_entry.has_later_siblings); stack.push(DrawStackEntry::VisitedChildren(draw_dag_entry)); let children: Vec = self .get_children_in_order(current_id) .into_iter() .filter(|child_id| active_activity_ids.contains(child_id)) .collect(); let has_children = !children.is_empty(); stack.extend(children.into_iter().map(|child_id| { DrawStackEntry::HasNotVisitedChildren(DrawDagEntry { activity_id: child_id, depth: current_depth.clone(), has_later_siblings: true, }) })); if has_children { stack .last_mut() .expect("If-statement ensured this is Some()") .set_no_later_siblings(); } } DrawStackEntry::VisitedChildren(draw_dag_entry) => { draw_order.push(draw_dag_entry); } }; } draw_order } fn get_children_in_order(&self, parent_id: u64) -> impl Iterator { let child_ids = self .activity_tree .iter() .filter(move |(_child_id, activity)| activity.get_parent_id() == parent_id) .flat_map(|(_child_id, activity)| TransparentIter::new(&self.activity_tree, activity)) .map(|activity| activity.get_activity_id()); child_ids } } enum ActivityState { Started { progress_numerator: u64, progress_denominator: u64, progress_running: u64, progress_failed: u64, }, Stopped, } impl Default for ActivityState { fn default() -> Self { ActivityState::Started { progress_numerator: 0, progress_denominator: 0, progress_running: 0, progress_failed: 0, } } } enum DrawStackEntry { HasNotVisitedChildren(DrawDagEntry), VisitedChildren(DrawDagEntry), } struct DrawDagEntry { activity_id: u64, depth: Vec, has_later_siblings: bool, } impl DrawStackEntry { fn set_no_later_siblings(&mut self) -> () { match self { DrawStackEntry::HasNotVisitedChildren(draw_dag_entry) => { draw_dag_entry.has_later_siblings = false } DrawStackEntry::VisitedChildren(draw_dag_entry) => { draw_dag_entry.has_later_siblings = false } }; } } struct PathToRootIter<'tree> { activity_tree: &'tree BTreeMap, next_activity_id: u64, } impl<'tree> PathToRootIter<'tree> { fn new( activity_tree: &'tree BTreeMap, next_activity_id: u64, ) -> PathToRootIter<'tree> { PathToRootIter { activity_tree, next_activity_id, } } } impl<'tree> Iterator for PathToRootIter<'tree> { type Item = u64; fn next(&mut self) -> Option { if self.next_activity_id == 0 { return None; } let next_activity = self.activity_tree.get(&self.next_activity_id).expect( "There shouldn't be a reference to an activity that does not exist in the tree.", ); self.next_activity_id = next_activity.get_parent_id(); Some(next_activity.get_activity_id()) } } /// An iterator that returns either the original activity if it is not transparent, or if it is transparent, the earliest non-transparent children. struct TransparentIter<'tree> { activity_tree: &'tree BTreeMap, parent: &'tree Activity, child_index: usize, } impl<'tree> TransparentIter<'tree> { fn new( activity_tree: &'tree BTreeMap, parent: &'tree Activity, ) -> TransparentIter<'tree> { TransparentIter { activity_tree, parent, child_index: 0, } } } impl<'tree> Iterator for TransparentIter<'tree> { type Item = &'tree Activity; fn next(&mut self) -> Option { if self.child_index == usize::max_value() { return None; } if !self.parent.is_transparent() { self.child_index = usize::max_value(); return Some(self.parent); } let mut visible_children_remaining = self.child_index; for (_child_id, child) in self.activity_tree.iter() { if child.get_parent_id() != self.parent.get_activity_id() { continue; } for visible_child in TransparentIter::new(self.activity_tree, child) { if visible_children_remaining > 0 { visible_children_remaining -= 1; } else { self.child_index += 1; return Some(visible_child); } } } return None; } } #[repr(u8)] enum Activity { Unknown(ActivityUnknown), CopyPath(ActivityCopyPath), FileTransfer(ActivityFileTransfer), Realize(ActivityRealize), CopyPaths(ActivityCopyPaths), Builds(ActivityBuilds), Build(ActivityBuild), OptimizeStore(ActivityOptimizeStore), VerifyPaths(ActivityVerifyPaths), Substitute(ActivitySubstitute), QueryPathInfo(ActivityQueryPathInfo), PostBuildHook(ActivityPostBuildHook), BuildWaiting(ActivityBuildWaiting), FetchTree(ActivityFetchTree), } impl Activity { fn stop(&mut self) -> () { match self { Activity::Unknown(activity_unknown) => { activity_unknown.state = ActivityState::Stopped; } Activity::CopyPath(activity_copy_path) => { activity_copy_path.state = ActivityState::Stopped; } Activity::FileTransfer(activity_file_transfer) => { activity_file_transfer.state = ActivityState::Stopped; } Activity::Realize(activity_realize) => { activity_realize.state = ActivityState::Stopped; } Activity::CopyPaths(activity_copy_paths) => { activity_copy_paths.state = ActivityState::Stopped; } Activity::Builds(activity_builds) => { activity_builds.state = ActivityState::Stopped; } Activity::Build(activity_build) => { activity_build.state = ActivityState::Stopped; } Activity::OptimizeStore(activity_optimize_store) => { activity_optimize_store.state = ActivityState::Stopped; } Activity::VerifyPaths(activity_verify_paths) => { activity_verify_paths.state = ActivityState::Stopped; } Activity::Substitute(activity_substitute) => { activity_substitute.state = ActivityState::Stopped; } Activity::QueryPathInfo(activity_query_path_info) => { activity_query_path_info.state = ActivityState::Stopped; } Activity::PostBuildHook(activity_post_build_hook) => { activity_post_build_hook.state = ActivityState::Stopped; } Activity::BuildWaiting(activity_build_waiting) => { activity_build_waiting.state = ActivityState::Stopped; } Activity::FetchTree(activity_fetch_tree) => { activity_fetch_tree.state = ActivityState::Stopped; } } } fn is_active(&self) -> bool { match self { Activity::Unknown(activity_unknown) => { matches!(activity_unknown.state, ActivityState::Started { .. }) } Activity::CopyPath(activity_copy_path) => { matches!(activity_copy_path.state, ActivityState::Started { .. }) } Activity::FileTransfer(activity_file_transfer) => { matches!(activity_file_transfer.state, ActivityState::Started { .. }) } Activity::Realize(activity_realize) => { matches!(activity_realize.state, ActivityState::Started { .. }) } Activity::CopyPaths(activity_copy_paths) => { matches!(activity_copy_paths.state, ActivityState::Started { .. }) } Activity::Builds(activity_builds) => { matches!(activity_builds.state, ActivityState::Started { .. }) } Activity::Build(activity_build) => { matches!(activity_build.state, ActivityState::Started { .. }) } Activity::OptimizeStore(activity_optimize_store) => { matches!(activity_optimize_store.state, ActivityState::Started { .. }) } Activity::VerifyPaths(activity_verify_paths) => { matches!(activity_verify_paths.state, ActivityState::Started { .. }) } Activity::Substitute(activity_substitute) => { matches!(activity_substitute.state, ActivityState::Started { .. }) } Activity::QueryPathInfo(activity_query_path_info) => { matches!( activity_query_path_info.state, ActivityState::Started { .. } ) } Activity::PostBuildHook(activity_post_build_hook) => { matches!( activity_post_build_hook.state, ActivityState::Started { .. } ) } Activity::BuildWaiting(activity_build_waiting) => { matches!(activity_build_waiting.state, ActivityState::Started { .. }) } Activity::FetchTree(activity_fetch_tree) => { matches!(activity_fetch_tree.state, ActivityState::Started { .. }) } } } fn is_transparent(&self) -> bool { match self { Activity::Unknown(_activity_unknown) => false, Activity::CopyPath(_activity_copy_path) => false, Activity::FileTransfer(_activity_file_transfer) => false, Activity::Realize(_activity_realize) => false, Activity::CopyPaths(_activity_copy_paths) => false, Activity::Builds(_activity_builds) => false, Activity::Build(_activity_build) => true, 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, } } fn display_name(&self) -> Option> { match self { Activity::Unknown(activity_unknown) => { warn!("Unimplemented display_name Unknown"); // TODO Some(Cow::Borrowed("Unknown")) } Activity::CopyPath(activity_copy_path) => { warn!("Unimplemented display_name CopyPath"); // TODO Some(Cow::Borrowed("CopyPath")) } Activity::FileTransfer(activity_file_transfer) => Some(Cow::Owned(format!( "FileTransfer({})", activity_file_transfer.url ))), Activity::Realize(activity_realize) => { // TODO what is a good display name for this? Maybe Realize() ? Some(Cow::Borrowed("Realize")) } Activity::CopyPaths(activity_copy_paths) => { warn!("Unimplemented display_name CopyPaths"); // TODO Some(Cow::Borrowed("CopyPaths")) } Activity::Builds(activity_builds) => { warn!("Unimplemented display_name Builds"); // TODO Some(Cow::Borrowed("Builds")) } Activity::Build(activity_build) => { if let Some(machine_name) = &activity_build.machine_name { Some(Cow::Owned(format!( "Build({}@{machine_name})", activity_build.drv_path ))) } else { Some(Cow::Owned(format!("Build({})", activity_build.drv_path))) } } Activity::OptimizeStore(activity_optimize_store) => { warn!("Unimplemented display_name OptimizeStore"); // TODO Some(Cow::Borrowed("OptimizeStore")) } Activity::VerifyPaths(activity_verify_paths) => { warn!("Unimplemented display_name VerifyPaths"); // TODO Some(Cow::Borrowed("VerifyPaths")) } Activity::Substitute(activity_substitute) => { warn!("Unimplemented display_name Substitute"); // TODO Some(Cow::Borrowed("Substitute")) } Activity::QueryPathInfo(activity_query_path_info) => { warn!("Unimplemented display_name QueryPathInfo"); // TODO Some(Cow::Borrowed("QueryPathInfo")) } Activity::PostBuildHook(activity_post_build_hook) => { warn!("Unimplemented display_name PostBuildHook"); // TODO Some(Cow::Borrowed("PostBuildHook")) } Activity::BuildWaiting(activity_build_waiting) => { warn!("Unimplemented display_name BuildWaiting"); // TODO Some(Cow::Borrowed("BuildWaiting")) } Activity::FetchTree(activity_fetch_tree) => { warn!("Unimplemented display_name FetchTree"); // TODO Some(Cow::Borrowed("FetchTree")) } } } } struct ActivityUnknown { id: u64, parent: u64, state: ActivityState, } struct ActivityCopyPath { id: u64, parent: u64, state: ActivityState, } struct ActivityFileTransfer { id: u64, parent: u64, state: ActivityState, url: String, } struct ActivityRealize { id: u64, parent: u64, state: ActivityState, } struct ActivityCopyPaths { id: u64, parent: u64, state: ActivityState, } struct ActivityBuilds { id: u64, parent: u64, state: ActivityState, } struct ActivityBuild { id: u64, parent: u64, state: ActivityState, drv_path: String, machine_name: Option, } struct ActivityOptimizeStore { id: u64, parent: u64, state: ActivityState, } struct ActivityVerifyPaths { id: u64, parent: u64, state: ActivityState, } struct ActivitySubstitute { id: u64, parent: u64, state: ActivityState, } struct ActivityQueryPathInfo { id: u64, parent: u64, state: ActivityState, } struct ActivityPostBuildHook { id: u64, parent: u64, state: ActivityState, } struct ActivityBuildWaiting { id: u64, state: ActivityState, } struct ActivityFetchTree { id: u64, parent: u64, state: ActivityState, } trait ActivityTreeEntry { fn get_activity_id(&self) -> u64; fn get_parent_id(&self) -> u64; } impl ActivityTreeEntry for Activity { fn get_activity_id(&self) -> u64 { match self { Activity::Unknown(activity_unknown) => activity_unknown.get_activity_id(), Activity::CopyPath(activity_copy_path) => activity_copy_path.get_activity_id(), Activity::FileTransfer(activity_file_transfer) => { activity_file_transfer.get_activity_id() } Activity::Realize(activity_realize) => activity_realize.get_activity_id(), Activity::CopyPaths(activity_copy_paths) => activity_copy_paths.get_activity_id(), Activity::Builds(activity_builds) => activity_builds.get_activity_id(), Activity::Build(activity_build) => activity_build.get_activity_id(), Activity::OptimizeStore(activity_optimize_store) => { activity_optimize_store.get_activity_id() } Activity::VerifyPaths(activity_verify_paths) => activity_verify_paths.get_activity_id(), Activity::Substitute(activity_substitute) => activity_substitute.get_activity_id(), Activity::QueryPathInfo(activity_query_path_info) => { activity_query_path_info.get_activity_id() } Activity::PostBuildHook(activity_post_build_hook) => { activity_post_build_hook.get_activity_id() } Activity::BuildWaiting(activity_build_waiting) => { activity_build_waiting.get_activity_id() } Activity::FetchTree(activity_fetch_tree) => activity_fetch_tree.get_activity_id(), } } fn get_parent_id(&self) -> u64 { match self { Activity::Unknown(activity_unknown) => activity_unknown.get_parent_id(), Activity::CopyPath(activity_copy_path) => activity_copy_path.get_parent_id(), Activity::FileTransfer(activity_file_transfer) => { activity_file_transfer.get_parent_id() } Activity::Realize(activity_realize) => activity_realize.get_parent_id(), Activity::CopyPaths(activity_copy_paths) => activity_copy_paths.get_parent_id(), Activity::Builds(activity_builds) => activity_builds.get_parent_id(), Activity::Build(activity_build) => activity_build.get_parent_id(), Activity::OptimizeStore(activity_optimize_store) => { activity_optimize_store.get_parent_id() } Activity::VerifyPaths(activity_verify_paths) => activity_verify_paths.get_parent_id(), Activity::Substitute(activity_substitute) => activity_substitute.get_parent_id(), Activity::QueryPathInfo(activity_query_path_info) => { activity_query_path_info.get_parent_id() } Activity::PostBuildHook(activity_post_build_hook) => { activity_post_build_hook.get_parent_id() } Activity::BuildWaiting(activity_build_waiting) => { activity_build_waiting.get_parent_id() } Activity::FetchTree(activity_fetch_tree) => activity_fetch_tree.get_parent_id(), } } } impl ActivityTreeEntry for ActivityUnknown { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { self.parent } } impl ActivityTreeEntry for ActivityCopyPath { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { self.parent } } impl ActivityTreeEntry for ActivityFileTransfer { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { self.parent } } impl ActivityTreeEntry for ActivityRealize { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { self.parent } } impl ActivityTreeEntry for ActivityCopyPaths { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { self.parent } } impl ActivityTreeEntry for ActivityBuilds { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { self.parent } } impl ActivityTreeEntry for ActivityBuild { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { self.parent } } impl ActivityTreeEntry for ActivityOptimizeStore { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { self.parent } } impl ActivityTreeEntry for ActivityVerifyPaths { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { self.parent } } impl ActivityTreeEntry for ActivitySubstitute { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { self.parent } } impl ActivityTreeEntry for ActivityQueryPathInfo { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { self.parent } } impl ActivityTreeEntry for ActivityPostBuildHook { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { self.parent } } impl ActivityTreeEntry for ActivityBuildWaiting { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { // Parent seems to always be zero? 0 } } impl ActivityTreeEntry for ActivityFetchTree { fn get_activity_id(&self) -> u64 { self.id } fn get_parent_id(&self) -> u64 { self.parent } }