Parse to a meaningful format in the nix output stream before hitting the running build.

This commit is contained in:
Tom Alexander
2026-02-19 18:39:01 -05:00
parent f797a19175
commit 8d2dd015c4
8 changed files with 1028 additions and 309 deletions

View File

@@ -1,21 +1,20 @@
use std::borrow::Cow;
use std::collections::BTreeMap;
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::Field;
use super::nix_output_stream::ActivityResultMessage;
use super::nix_output_stream::NixMessage;
const ACTIVITY_TYPE_: i32 = 10;
pub(crate) struct RunningBuild<'db> {
db_handle: &'db DbHandle,
activity_tree: BTreeMap<u64, Activity>,
@@ -62,7 +61,7 @@ impl<'db> RunningBuild<'db> {
}
let exit_status = exit_status_handle.await?;
println!("nixos-rebuild status was: {}", exit_status);
println!("nix build status was: {}", exit_status);
let update: u64 =
sqlx::query(r#"UPDATE build SET end_time=unixepoch('now'), status=? WHERE id=?"#)
@@ -92,230 +91,136 @@ impl<'db> RunningBuild<'db> {
}
NixMessage::Action(nix_action) => nix_action,
};
match &message {
NixAction::Msg {
level,
msg,
raw_msg,
file,
line,
column,
} => {
match message {
NixAction::Msg(msg_message) => {
// For now we can ignore the messages.
}
NixAction::Start {
id,
fields,
level,
parent,
text,
r#type,
} => {
let entry = self.activity_tree.entry(*id);
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 r#type {
0 => {
match activity_start_message {
ActivityStartMessage::Unknown(activity_start_unknown) => {
entry.insert(Activity::Unknown(ActivityUnknown {
id: *id,
parent: *parent,
id: activity_start_unknown.id,
parent: activity_start_unknown.parent,
state: ActivityState::default(),
level: *level,
text: text.to_owned(),
level: activity_start_unknown.level,
text: activity_start_unknown.text,
}));
}
100 => {
// TODO: Haven't seen any of these.
warn!("Found CopyPath: {}", serde_json::to_string(&message)?);
ActivityStartMessage::CopyPath(activity_start_copy_path) => {
entry.insert(Activity::CopyPath(ActivityCopyPath {
id: *id,
parent: *parent,
id: activity_start_copy_path.id,
parent: activity_start_copy_path.parent,
state: ActivityState::default(),
}));
}
101 => {
match fields {
Some(f) if f.len() > 1 => {
warn!(
"Found more than one field in ActivityFileTransfer: {}",
serde_json::to_string(&message)?
);
}
_ => {}
};
ActivityStartMessage::FileTransfer(activity_start_file_transfer) => {
entry.insert(Activity::FileTransfer(ActivityFileTransfer {
id: *id,
parent: *parent,
id: activity_start_file_transfer.id,
parent: activity_start_file_transfer.parent,
state: ActivityState::default(),
level: *level,
text: text.to_owned(),
url: string_field(fields, 0).to_owned(),
text: activity_start_file_transfer.text,
url: activity_start_file_transfer.url,
}));
}
102 => {
match fields {
Some(f) if f.len() > 0 => {
warn!(
"Found fields in ActivityRealize: {}",
serde_json::to_string(&message)?
);
}
_ => {}
};
if text.len() > 0 {
warn!(
"Found Realize with text: {}",
serde_json::to_string(&message)?
);
}
ActivityStartMessage::Realize(activity_start_realize) => {
entry.insert(Activity::Realize(ActivityRealize {
id: *id,
parent: *parent,
id: activity_start_realize.id,
parent: activity_start_realize.parent,
state: ActivityState::default(),
level: *level,
text: text.to_owned(),
}));
}
103 => {
match fields {
Some(f) if f.len() > 0 => {
warn!(
"Found fields in CopyPaths: {}",
serde_json::to_string(&message)?
);
}
_ => {}
};
if text.len() > 0 {
warn!(
"Found CopyPaths with text: {}",
serde_json::to_string(&message)?
);
}
ActivityStartMessage::CopyPaths(activity_start_copy_paths) => {
entry.insert(Activity::CopyPaths(ActivityCopyPaths {
id: *id,
parent: *parent,
id: activity_start_copy_paths.id,
parent: activity_start_copy_paths.parent,
state: ActivityState::default(),
level: *level,
text: text.to_owned(),
level: activity_start_copy_paths.level,
text: activity_start_copy_paths.text,
}));
}
104 => {
match fields {
Some(f) if f.len() > 0 => {
warn!(
"Found fields in Builds: {}",
serde_json::to_string(&message)?
);
}
_ => {}
};
if text.len() > 0 {
warn!(
"Found Builds with text: {}",
serde_json::to_string(&message)?
);
}
ActivityStartMessage::Builds(activity_start_builds) => {
entry.insert(Activity::Builds(ActivityBuilds {
id: *id,
parent: *parent,
id: activity_start_builds.id,
parent: activity_start_builds.parent,
state: ActivityState::default(),
level: *level,
text: text.to_owned(),
}));
}
105 => {
// TODO: What are the other fields ["/nix/store/j54kvd8mlj8cl9ycvlkh5987fqvzl4p5-m4-1.4.20.tar.bz2.drv","",1,1]
ActivityStartMessage::Build(activity_start_build) => {
entry.insert(Activity::Build(ActivityBuild {
id: *id,
parent: *parent,
id: activity_start_build.id,
parent: activity_start_build.parent,
state: ActivityState::default(),
level: *level,
text: text.to_owned(),
path: string_field(fields, 0).to_owned(),
text: activity_start_build.text,
drv_path: activity_start_build.drv_path,
}));
}
106 => {
// TODO: Haven't seen any of these.
warn!("Found OptimizeStore: {}", serde_json::to_string(&message)?);
ActivityStartMessage::OptimizeStore(activity_start_optimize_store) => {
entry.insert(Activity::OptimizeStore(ActivityOptimizeStore {
id: *id,
parent: *parent,
id: activity_start_optimize_store.id,
parent: activity_start_optimize_store.parent,
state: ActivityState::default(),
}));
}
107 => {
// TODO: Haven't seen any of these.
warn!("Found VerifyPath: {}", serde_json::to_string(&message)?);
ActivityStartMessage::VerifyPaths(activity_start_verify_paths) => {
entry.insert(Activity::VerifyPaths(ActivityVerifyPaths {
id: *id,
parent: *parent,
id: activity_start_verify_paths.id,
parent: activity_start_verify_paths.parent,
state: ActivityState::default(),
}));
}
108 => {
// TODO: Haven't seen any of these.
warn!("Found Subtitute: {}", serde_json::to_string(&message)?);
ActivityStartMessage::Substitute(activity_start_substitute) => {
entry.insert(Activity::Substitute(ActivitySubstitute {
id: *id,
parent: *parent,
id: activity_start_substitute.id,
parent: activity_start_substitute.parent,
state: ActivityState::default(),
}));
}
109 => {
// TODO: Haven't seen any of these.
warn!("Found QueryPathInfo: {}", serde_json::to_string(&message)?);
ActivityStartMessage::QueryPathInfo(activity_start_query_path_info) => {
entry.insert(Activity::QueryPathInfo(ActivityQueryPathInfo {
id: *id,
parent: *parent,
id: activity_start_query_path_info.id,
parent: activity_start_query_path_info.parent,
state: ActivityState::default(),
}));
}
110 => {
// TODO: Haven't seen any of these.
warn!("Found PostBuildHook: {}", serde_json::to_string(&message)?);
ActivityStartMessage::PostBuildHook(activity_start_post_build_hook) => {
entry.insert(Activity::PostBuildHook(ActivityPostBuildHook {
id: *id,
parent: *parent,
id: activity_start_post_build_hook.id,
parent: activity_start_post_build_hook.parent,
state: ActivityState::default(),
}));
}
111 => {
// TODO: Haven't seen any of these.
warn!("Found BuildWaiting: {}", serde_json::to_string(&message)?);
ActivityStartMessage::BuildWaiting(activity_start_build_waiting) => {
entry.insert(Activity::BuildWaiting(ActivityBuildWaiting {
id: *id,
parent: *parent,
id: activity_start_build_waiting.id,
state: ActivityState::default(),
}));
}
112 => {
// TODO: Haven't seen any of these.
warn!("Found FetchTree: {}", serde_json::to_string(&message)?);
ActivityStartMessage::FetchTree(activity_start_fetch_tree) => {
entry.insert(Activity::FetchTree(ActivityFetchTree {
id: *id,
parent: *parent,
id: activity_start_fetch_tree.id,
parent: activity_start_fetch_tree.parent,
state: ActivityState::default(),
}));
}
_ => {
panic!(
"Unhandled start activity: {}",
serde_json::to_string(&message)?
);
}
};
self.print_current_status();
}
NixAction::Stop { id } => {
let entry = self.activity_tree.entry(*id);
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: {id}");
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();
@@ -324,56 +229,52 @@ impl<'db> RunningBuild<'db> {
self.print_current_status();
// println!("{}", serde_json::to_string(&message)?);
}
NixAction::Result { id, fields, r#type } => {
match r#type {
100 => {
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)?);
// warn!("Found FileLinked: {}", serde_json::to_string(&message)?);
}
101 => {
ActivityResultMessage::BuildLogLine(activity_result_build_log_line) => {
// BuildLogLine
// The first field is a string containing the log line
}
102 => {
ActivityResultMessage::UntrustedPath(activity_result_untrusted_path) => {
// UntrustedPath
// TODO: Haven't seen any of these.
warn!("Found UntrustedPath: {}", serde_json::to_string(&message)?);
// warn!("Found UntrustedPath: {}", serde_json::to_string(&message)?);
}
103 => {
ActivityResultMessage::CorruptedPath(activity_result_corrupted_path) => {
// CorruptedPath
// TODO: Haven't seen any of these.
warn!("Found CorruptedPath: {}", serde_json::to_string(&message)?);
// warn!("Found CorruptedPath: {}", serde_json::to_string(&message)?);
}
104 => {
// SetPhase
ActivityResultMessage::SetPhase(activity_result_set_phase) => { // SetPhase
// The first field is the phase name
}
105 => {
// Progress
ActivityResultMessage::Progress(activity_result_progress) => { // Progress
// Fields numerator, denominator, running?, failed?
}
106 => {
// SetExpected
ActivityResultMessage::SetExpected(activity_result_set_expected) => { // SetExpected
// Fields activity type?, expected?
}
107 => {
ActivityResultMessage::PostBuildLogLine(
activity_result_post_build_log_line,
) => {
// PostBuildLogLine
// TODO: Haven't seen any of these.
warn!(
"Found PostBuildLogLine: {}",
serde_json::to_string(&message)?
);
// warn!(
// "Found PostBuildLogLine: {}",
// serde_json::to_string(&message)?
// );
}
108 => {
ActivityResultMessage::FetchStatus(activity_result_fetch_status) => {
// FetchStatus
// TODO: Haven't seen any of these.
warn!("Found FetchStatus: {}", serde_json::to_string(&message)?);
// warn!("Found FetchStatus: {}", serde_json::to_string(&message)?);
// println!("{}", serde_json::to_string(&message)?);
}
_ => {
panic!("Unhandled result: {}", serde_json::to_string(&message)?);
}
};
}
};
@@ -381,41 +282,15 @@ impl<'db> RunningBuild<'db> {
}
fn print_current_status(&self) -> () {
// let in_progress = self
// .activity_tree
// .iter()
// .filter(|activity: &(&u64, &ActivityTreeEntry)| activity.1.is_active());
// let names: Vec<&str> = in_progress
// .map(|activity| match activity.1 {
// ActivityTreeEntry::Unknown {
// id,
// parent,
// r#type,
// state,
// } => "unknown",
// ActivityTreeEntry::System {
// id,
// parent,
// r#type,
// state,
// } => "system",
// ActivityTreeEntry::Download {
// id,
// parent,
// r#type,
// state,
// url,
// } => url,
// ActivityTreeEntry::Build {
// id,
// parent,
// r#type,
// state,
// path,
// } => path,
// })
// .collect();
// let name_list = names.join(", ");
let in_progress = self
.activity_tree
.iter()
.filter(|activity: &(&u64, &Activity)| activity.1.is_active());
let names: Vec<Cow<'_, str>> = in_progress
.filter_map(|(id, activity)| activity.display_name())
.collect();
let name_list = names.join(", ");
// TODO: Make a meaningful current status.
// println!("In progress: {name_list}");
}
}
@@ -460,25 +335,6 @@ enum Activity {
}
impl Activity {
fn get_type(&self) -> u8 {
match self {
Activity::Unknown(_) => 0,
Activity::CopyPath(_) => 100,
Activity::FileTransfer(_) => 101,
Activity::Realize(_) => 102,
Activity::CopyPaths(_) => 103,
Activity::Builds(_) => 104,
Activity::Build(_) => 105,
Activity::OptimizeStore(_) => 106,
Activity::VerifyPaths(_) => 107,
Activity::Substitute(_) => 108,
Activity::QueryPathInfo(_) => 109,
Activity::PostBuildHook(_) => 110,
Activity::BuildWaiting(_) => 111,
Activity::FetchTree(_) => 112,
}
}
fn stop(&mut self) -> () {
match self {
Activity::Unknown(activity_unknown) => {
@@ -525,6 +381,120 @@ impl Activity {
}
}
}
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 display_name(&self) -> Option<Cow<'_, str>> {
match self {
Activity::Unknown(activity_unknown) => {
// TODO
Some(Cow::Borrowed("Unknown"))
}
Activity::CopyPath(activity_copy_path) => {
// TODO
Some(Cow::Borrowed("CopyPath"))
}
Activity::FileTransfer(activity_file_transfer) => {
// TODO
Some(Cow::Borrowed("FileTransfer"))
}
Activity::Realize(activity_realize) => {
// TODO
Some(Cow::Borrowed("Realize"))
}
Activity::CopyPaths(activity_copy_paths) => {
// TODO
Some(Cow::Borrowed("CopyPaths"))
}
Activity::Builds(activity_builds) => {
// TODO
Some(Cow::Borrowed("Builds"))
}
Activity::Build(activity_build) => {
// TODO
Some(Cow::Borrowed("Build"))
}
Activity::OptimizeStore(activity_optimize_store) => {
// TODO
Some(Cow::Borrowed("OptimizeStore"))
}
Activity::VerifyPaths(activity_verify_paths) => {
// TODO
Some(Cow::Borrowed("VerifyPaths"))
}
Activity::Substitute(activity_substitute) => {
// TODO
Some(Cow::Borrowed("Substitute"))
}
Activity::QueryPathInfo(activity_query_path_info) => {
// TODO
Some(Cow::Borrowed("QueryPathInfo"))
}
Activity::PostBuildHook(activity_post_build_hook) => {
// TODO
Some(Cow::Borrowed("PostBuildHook"))
}
Activity::BuildWaiting(activity_build_waiting) => {
// TODO
Some(Cow::Borrowed("BuildWaiting"))
}
Activity::FetchTree(activity_fetch_tree) => {
// TODO
Some(Cow::Borrowed("FetchTree"))
}
}
}
}
struct ActivityUnknown {
@@ -543,7 +513,6 @@ struct ActivityFileTransfer {
id: u64,
parent: u64,
state: ActivityState,
level: u8,
text: String,
url: String,
}
@@ -551,8 +520,6 @@ struct ActivityRealize {
id: u64,
parent: u64,
state: ActivityState,
level: u8,
text: String,
}
struct ActivityCopyPaths {
id: u64,
@@ -565,16 +532,13 @@ struct ActivityBuilds {
id: u64,
parent: u64,
state: ActivityState,
level: u8,
text: String,
}
struct ActivityBuild {
id: u64,
parent: u64,
state: ActivityState,
level: u8,
text: String,
path: String,
drv_path: String,
}
struct ActivityOptimizeStore {
id: u64,
@@ -603,7 +567,6 @@ struct ActivityPostBuildHook {
}
struct ActivityBuildWaiting {
id: u64,
parent: u64,
state: ActivityState,
}
struct ActivityFetchTree {
@@ -611,51 +574,3 @@ struct ActivityFetchTree {
parent: u64,
state: ActivityState,
}
enum ActivityResult {
FileLinked(ResultFileLinked),
BuildLogLine(ResultBuildLogLine),
UntrustedPath(ResultUntrustedPath),
CorruptedPath(ResultCorruptedPath),
SetPhase(ResultSetPhase),
Progress(ResultProgress),
SetExpected(ResultSetExpected),
PostBuildLogLine(ResultPostBuildLogLine),
FetchStatus(ResultFetchStatus),
}
impl ActivityResult {
fn get_type(&self) -> u8 {
match self {
ActivityResult::FileLinked(_) => 100,
ActivityResult::BuildLogLine(_) => 101,
ActivityResult::UntrustedPath(_) => 102,
ActivityResult::CorruptedPath(_) => 103,
ActivityResult::SetPhase(_) => 104,
ActivityResult::Progress(_) => 105,
ActivityResult::SetExpected(_) => 106,
ActivityResult::PostBuildLogLine(_) => 107,
ActivityResult::FetchStatus(_) => 108,
}
}
}
struct ResultFileLinked {}
struct ResultBuildLogLine {}
struct ResultUntrustedPath {}
struct ResultCorruptedPath {}
struct ResultSetPhase {}
struct ResultProgress {}
struct ResultSetExpected {}
struct ResultPostBuildLogLine {}
struct ResultFetchStatus {}
fn string_field(fields: &Option<Vec<Field>>, ind: usize) -> &String {
match fields {
Some(fields) => match &fields[ind] {
Field::Number(_n) => panic!("Expected field {ind} to be text, but it is a number."),
Field::Text(t) => t,
},
None => panic!("Expected fields but no fields present."),
}
}