Interpret the nix logs.
This commit is contained in:
9
README.org
Normal file
9
README.org
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
* Example invocations
|
||||||
|
** Run build
|
||||||
|
#+begin_src bash
|
||||||
|
RUST_BACKTRACE=1 RUST_LOG=nix_builder=DEBUG cargo run -- build --config example_config.toml --target odo
|
||||||
|
#+end_src
|
||||||
|
** Feed logs
|
||||||
|
#+begin_src bash
|
||||||
|
RUST_BACKTRACE=1 RUST_LOG=nix_builder=DEBUG cargo run -- feed-log --input ~/persist/tmp/nix_build
|
||||||
|
#+end_src
|
||||||
@@ -20,6 +20,9 @@ pub(crate) enum Commands {
|
|||||||
|
|
||||||
/// Launch a daemon to run builds.
|
/// Launch a daemon to run builds.
|
||||||
Daemon(DaemonArgs),
|
Daemon(DaemonArgs),
|
||||||
|
|
||||||
|
/// Feed nix logs into the parser for development.
|
||||||
|
FeedLog(FeedLogArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
@@ -40,6 +43,13 @@ pub(crate) struct DaemonArgs {
|
|||||||
pub(crate) path: PathBuf,
|
pub(crate) path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub(crate) struct FeedLogArgs {
|
||||||
|
/// Path to the log file.
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub(crate) input: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
2
src/command/feed_logs/mod.rs
Normal file
2
src/command/feed_logs/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
mod runner;
|
||||||
|
pub(crate) use runner::feed_logs;
|
||||||
55
src/command/feed_logs/runner.rs
Normal file
55
src/command/feed_logs/runner.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use tokio::fs::File;
|
||||||
|
use tokio::io::AsyncBufReadExt;
|
||||||
|
use tokio::io::BufReader;
|
||||||
|
use tokio::io::Lines;
|
||||||
|
|
||||||
|
use crate::cli::parameters::FeedLogArgs;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
use crate::database::db_handle::DbHandle;
|
||||||
|
use crate::nix_util::NixOutputStream;
|
||||||
|
use crate::nix_util::OutputLine;
|
||||||
|
use crate::nix_util::OutputLineStream;
|
||||||
|
use crate::nix_util::RunningBuild;
|
||||||
|
|
||||||
|
pub(crate) async fn feed_logs(args: FeedLogArgs) -> Result<()> {
|
||||||
|
let db_handle = DbHandle::new::<String>(None).await?;
|
||||||
|
let mut running_build = RunningBuild::new(&db_handle)?;
|
||||||
|
let file_stream = FileStream::new(args.input).await?;
|
||||||
|
let mut nix_output_stream = NixOutputStream::new(file_stream);
|
||||||
|
while let Some(message) = nix_output_stream.next().await? {
|
||||||
|
running_build.handle_message(message)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FileStream {
|
||||||
|
lines: Lines<BufReader<File>>,
|
||||||
|
file_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileStream {
|
||||||
|
pub(crate) async fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||||
|
let f = File::open(&path).await?;
|
||||||
|
let foo = BufReader::new(f).lines();
|
||||||
|
Ok(FileStream {
|
||||||
|
lines: foo,
|
||||||
|
file_path: path.as_ref().to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputLineStream for FileStream {
|
||||||
|
async fn next_line(&mut self) -> Result<OutputLine> {
|
||||||
|
let line = self.lines.next_line().await?;
|
||||||
|
if let Some(l) = line {
|
||||||
|
Ok(OutputLine::Stdout(l))
|
||||||
|
} else {
|
||||||
|
Ok(OutputLine::Done)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
pub(crate) mod build;
|
pub(crate) mod build;
|
||||||
pub(crate) mod daemon;
|
pub(crate) mod daemon;
|
||||||
|
pub(crate) mod feed_logs;
|
||||||
|
|||||||
42
src/feed_log.rs
Normal file
42
src/feed_log.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use std::process::ExitCode;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use self::cli::parameters::Cli;
|
||||||
|
use self::cli::parameters::Commands;
|
||||||
|
use self::command::build::run_build;
|
||||||
|
use self::command::daemon::start_daemon;
|
||||||
|
use self::error::CustomError;
|
||||||
|
use self::init_tracing::init_telemetry;
|
||||||
|
use self::init_tracing::shutdown_telemetry;
|
||||||
|
mod cli;
|
||||||
|
mod command;
|
||||||
|
mod config;
|
||||||
|
mod database;
|
||||||
|
mod error;
|
||||||
|
mod fs_util;
|
||||||
|
mod git_util;
|
||||||
|
mod init_tracing;
|
||||||
|
mod nix_util;
|
||||||
|
|
||||||
|
pub(crate) type Result<T> = std::result::Result<T, CustomError>;
|
||||||
|
|
||||||
|
fn main() -> Result<ExitCode> {
|
||||||
|
let rt = tokio::runtime::Runtime::new()?;
|
||||||
|
rt.block_on(async { main_body().await })
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn main_body() -> Result<ExitCode> {
|
||||||
|
init_telemetry().expect("Telemetry should initialize successfully.");
|
||||||
|
let args = Cli::parse();
|
||||||
|
match args.command {
|
||||||
|
Commands::Build(args) => {
|
||||||
|
run_build(args).await?;
|
||||||
|
}
|
||||||
|
Commands::Daemon(args) => {
|
||||||
|
start_daemon(args).await?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
shutdown_telemetry().expect("Telemetry should shutdown successfully.");
|
||||||
|
Ok(ExitCode::SUCCESS)
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use self::cli::parameters::Cli;
|
|||||||
use self::cli::parameters::Commands;
|
use self::cli::parameters::Commands;
|
||||||
use self::command::build::run_build;
|
use self::command::build::run_build;
|
||||||
use self::command::daemon::start_daemon;
|
use self::command::daemon::start_daemon;
|
||||||
|
use self::command::feed_logs::feed_logs;
|
||||||
use self::error::CustomError;
|
use self::error::CustomError;
|
||||||
use self::init_tracing::init_telemetry;
|
use self::init_tracing::init_telemetry;
|
||||||
use self::init_tracing::shutdown_telemetry;
|
use self::init_tracing::shutdown_telemetry;
|
||||||
@@ -36,6 +37,9 @@ async fn main_body() -> Result<ExitCode> {
|
|||||||
Commands::Daemon(args) => {
|
Commands::Daemon(args) => {
|
||||||
start_daemon(args).await?;
|
start_daemon(args).await?;
|
||||||
}
|
}
|
||||||
|
Commands::FeedLog(args) => {
|
||||||
|
feed_logs(args).await?;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
shutdown_telemetry().expect("Telemetry should shutdown successfully.");
|
shutdown_telemetry().expect("Telemetry should shutdown successfully.");
|
||||||
Ok(ExitCode::SUCCESS)
|
Ok(ExitCode::SUCCESS)
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ where
|
|||||||
reference
|
reference
|
||||||
};
|
};
|
||||||
|
|
||||||
// nixos-rebuild build --show-trace --sudo --max-jobs "$JOBS" --flake "$DIR/../../#odo" --log-format internal-json -v "${@}"
|
|
||||||
let mut command = Command::new("nixos-rebuild");
|
let mut command = Command::new("nixos-rebuild");
|
||||||
command.current_dir(build_path);
|
command.current_dir(build_path);
|
||||||
command.stdout(Stdio::piped());
|
command.stdout(Stdio::piped());
|
||||||
@@ -47,7 +46,7 @@ where
|
|||||||
"1",
|
"1",
|
||||||
"--log-format",
|
"--log-format",
|
||||||
"internal-json",
|
"internal-json",
|
||||||
"-v",
|
"-vvvvvvvvvvv",
|
||||||
"--keep-going",
|
"--keep-going",
|
||||||
]);
|
]);
|
||||||
command.arg("--flake");
|
command.arg("--flake");
|
||||||
|
|||||||
@@ -3,3 +3,7 @@ mod nix_output_stream;
|
|||||||
mod output_stream;
|
mod output_stream;
|
||||||
mod running_build;
|
mod running_build;
|
||||||
pub(crate) use high_level::*;
|
pub(crate) use high_level::*;
|
||||||
|
pub(crate) use nix_output_stream::NixOutputStream;
|
||||||
|
pub(crate) use output_stream::OutputLine;
|
||||||
|
pub(crate) use output_stream::OutputLineStream;
|
||||||
|
pub(crate) use running_build::RunningBuild;
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ use serde::Deserialize;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use super::output_stream::OutputStream;
|
use super::output_stream::OutputLineStream;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
pub(crate) struct NixOutputStream {
|
pub(crate) struct NixOutputStream<S> {
|
||||||
inner: OutputStream,
|
inner: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NixOutputStream {
|
impl<S: OutputLineStream> NixOutputStream<S> {
|
||||||
pub(crate) fn new(inner: OutputStream) -> Self {
|
pub(crate) fn new(inner: S) -> Self {
|
||||||
NixOutputStream { inner }
|
NixOutputStream { inner }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,6 @@ impl NixOutputStream {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if !line.starts_with("@nix ") {
|
if !line.starts_with("@nix ") {
|
||||||
println!("FAIL PARSE: {line}");
|
|
||||||
return Ok(Some(NixMessage::ParseFailure(line)));
|
return Ok(Some(NixMessage::ParseFailure(line)));
|
||||||
}
|
}
|
||||||
let payload = &line[4..];
|
let payload = &line[4..];
|
||||||
@@ -34,32 +33,46 @@ impl NixOutputStream {
|
|||||||
return Ok(Some(NixMessage::Action(action)));
|
return Ok(Some(NixMessage::Action(action)));
|
||||||
}
|
}
|
||||||
if let Ok(parsed) = serde_json::from_str(&payload) {
|
if let Ok(parsed) = serde_json::from_str(&payload) {
|
||||||
println!("GENERIC PARSE: {line}");
|
return Ok(Some(NixMessage::Generic(parsed, line)));
|
||||||
return Ok(Some(NixMessage::Generic(parsed)));
|
|
||||||
}
|
}
|
||||||
println!("FAIL PARSE: {line}");
|
|
||||||
Ok(Some(NixMessage::ParseFailure(line)))
|
Ok(Some(NixMessage::ParseFailure(line)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) enum NixMessage {
|
pub(crate) enum NixMessage {
|
||||||
ParseFailure(String),
|
ParseFailure(String),
|
||||||
Generic(Value),
|
Generic(Value, String),
|
||||||
Action(NixAction),
|
Action(NixAction),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(tag = "action", rename_all = "lowercase")]
|
#[serde(tag = "action", rename_all = "lowercase", deny_unknown_fields)]
|
||||||
pub(crate) enum NixAction {
|
pub(crate) enum NixAction {
|
||||||
Msg {
|
Msg {
|
||||||
level: u8,
|
level: u8,
|
||||||
msg: String,
|
msg: String,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
raw_msg: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
file: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
line: Option<u64>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
column: Option<u64>,
|
||||||
},
|
},
|
||||||
Start {
|
Start {
|
||||||
id: u64,
|
id: u64,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
fields: Option<Vec<Field>>,
|
||||||
|
|
||||||
level: u8,
|
level: u8,
|
||||||
parent: u8,
|
parent: u64,
|
||||||
text: String,
|
text: String,
|
||||||
r#type: u8,
|
r#type: u8,
|
||||||
},
|
},
|
||||||
@@ -73,9 +86,9 @@ pub(crate) enum NixAction {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged, deny_unknown_fields)]
|
||||||
pub(crate) enum Field {
|
pub(crate) enum Field {
|
||||||
Number(u16),
|
Number(u64),
|
||||||
Text(String),
|
Text(String),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ use tokio::process::ChildStdout;
|
|||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
|
pub(crate) trait OutputLineStream {
|
||||||
|
async fn next_line(&mut self) -> Result<OutputLine>;
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct OutputStream {
|
pub(crate) struct OutputStream {
|
||||||
stdout: Option<Lines<BufReader<ChildStdout>>>,
|
stdout: Option<Lines<BufReader<ChildStdout>>>,
|
||||||
stderr: Option<Lines<BufReader<ChildStderr>>>,
|
stderr: Option<Lines<BufReader<ChildStderr>>>,
|
||||||
@@ -29,8 +33,10 @@ impl OutputStream {
|
|||||||
stderr: Some(stderr),
|
stderr: Some(stderr),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn next_line(&mut self) -> Result<OutputLine> {
|
impl OutputLineStream for OutputStream {
|
||||||
|
async fn next_line(&mut self) -> Result<OutputLine> {
|
||||||
loop {
|
loop {
|
||||||
match (&mut self.stdout, &mut self.stderr) {
|
match (&mut self.stdout, &mut self.stderr) {
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
|
|||||||
@@ -1,20 +1,32 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use sqlx::Row;
|
use sqlx::Row;
|
||||||
use tokio::process::Child;
|
use tokio::process::Child;
|
||||||
|
use tracing::error;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::database::db_handle::DbHandle;
|
use crate::database::db_handle::DbHandle;
|
||||||
|
use crate::nix_util::nix_output_stream::NixAction;
|
||||||
use crate::nix_util::nix_output_stream::NixOutputStream;
|
use crate::nix_util::nix_output_stream::NixOutputStream;
|
||||||
use crate::nix_util::output_stream::OutputStream;
|
use crate::nix_util::output_stream::OutputStream;
|
||||||
|
|
||||||
|
use super::nix_output_stream::Field;
|
||||||
use super::nix_output_stream::NixMessage;
|
use super::nix_output_stream::NixMessage;
|
||||||
|
|
||||||
|
const ACTIVITY_TYPE_: i32 = 10;
|
||||||
|
|
||||||
pub(crate) struct RunningBuild<'db> {
|
pub(crate) struct RunningBuild<'db> {
|
||||||
db_handle: &'db DbHandle,
|
db_handle: &'db DbHandle,
|
||||||
|
activity_tree: BTreeMap<u64, Activity>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> RunningBuild<'db> {
|
impl<'db> RunningBuild<'db> {
|
||||||
pub(crate) fn new(db_handle: &'db DbHandle) -> Result<Self> {
|
pub(crate) fn new(db_handle: &'db DbHandle) -> Result<Self> {
|
||||||
Ok(RunningBuild { db_handle })
|
Ok(RunningBuild {
|
||||||
|
db_handle,
|
||||||
|
activity_tree: BTreeMap::new(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_to_completion<TN>(
|
pub(crate) async fn run_to_completion<TN>(
|
||||||
@@ -34,7 +46,8 @@ impl<'db> RunningBuild<'db> {
|
|||||||
.try_get("id")?;
|
.try_get("id")?;
|
||||||
|
|
||||||
let output_stream = OutputStream::from_child(&mut child)?;
|
let output_stream = OutputStream::from_child(&mut child)?;
|
||||||
let mut nix_output_stream = NixOutputStream::new(output_stream);
|
let mut nix_output_stream: NixOutputStream<OutputStream> =
|
||||||
|
NixOutputStream::new(output_stream);
|
||||||
|
|
||||||
let exit_status_handle = tokio::spawn(async move {
|
let exit_status_handle = tokio::spawn(async move {
|
||||||
let status = child
|
let status = child
|
||||||
@@ -45,7 +58,6 @@ impl<'db> RunningBuild<'db> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
while let Some(message) = nix_output_stream.next().await? {
|
while let Some(message) = nix_output_stream.next().await? {
|
||||||
// foo
|
|
||||||
self.handle_message(message)?;
|
self.handle_message(message)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,8 +80,582 @@ impl<'db> RunningBuild<'db> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_message(&mut self, message: NixMessage) -> Result<()> {
|
pub(crate) fn handle_message(&mut self, message: NixMessage) -> Result<()> {
|
||||||
// println!("OUT: {:?}", message);
|
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 {
|
||||||
|
level,
|
||||||
|
msg,
|
||||||
|
raw_msg,
|
||||||
|
file,
|
||||||
|
line,
|
||||||
|
column,
|
||||||
|
} => {
|
||||||
|
// For now we can ignore the messages.
|
||||||
|
}
|
||||||
|
NixAction::Start {
|
||||||
|
id,
|
||||||
|
fields,
|
||||||
|
level,
|
||||||
|
parent,
|
||||||
|
text,
|
||||||
|
r#type,
|
||||||
|
} => {
|
||||||
|
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 => {
|
||||||
|
entry.insert(Activity::Unknown(ActivityUnknown {
|
||||||
|
id: *id,
|
||||||
|
parent: *parent,
|
||||||
|
state: ActivityState::default(),
|
||||||
|
level: *level,
|
||||||
|
text: text.to_owned(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
100 => {
|
||||||
|
// TODO: Haven't seen any of these.
|
||||||
|
warn!("Found CopyPath: {}", serde_json::to_string(&message)?);
|
||||||
|
entry.insert(Activity::CopyPath(ActivityCopyPath {
|
||||||
|
id: *id,
|
||||||
|
parent: *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)?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
entry.insert(Activity::FileTransfer(ActivityFileTransfer {
|
||||||
|
id: *id,
|
||||||
|
parent: *parent,
|
||||||
|
state: ActivityState::default(),
|
||||||
|
level: *level,
|
||||||
|
text: text.to_owned(),
|
||||||
|
url: string_field(fields, 0).to_owned(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
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)?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
entry.insert(Activity::Realize(ActivityRealize {
|
||||||
|
id: *id,
|
||||||
|
parent: *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)?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
entry.insert(Activity::CopyPaths(ActivityCopyPaths {
|
||||||
|
id: *id,
|
||||||
|
parent: *parent,
|
||||||
|
state: ActivityState::default(),
|
||||||
|
level: *level,
|
||||||
|
text: text.to_owned(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
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)?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
entry.insert(Activity::Builds(ActivityBuilds {
|
||||||
|
id: *id,
|
||||||
|
parent: *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]
|
||||||
|
entry.insert(Activity::Build(ActivityBuild {
|
||||||
|
id: *id,
|
||||||
|
parent: *parent,
|
||||||
|
state: ActivityState::default(),
|
||||||
|
level: *level,
|
||||||
|
text: text.to_owned(),
|
||||||
|
path: string_field(fields, 0).to_owned(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
106 => {
|
||||||
|
// TODO: Haven't seen any of these.
|
||||||
|
warn!("Found OptimizeStore: {}", serde_json::to_string(&message)?);
|
||||||
|
entry.insert(Activity::OptimizeStore(ActivityOptimizeStore {
|
||||||
|
id: *id,
|
||||||
|
parent: *parent,
|
||||||
|
state: ActivityState::default(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
107 => {
|
||||||
|
// TODO: Haven't seen any of these.
|
||||||
|
warn!("Found VerifyPath: {}", serde_json::to_string(&message)?);
|
||||||
|
entry.insert(Activity::VerifyPaths(ActivityVerifyPaths {
|
||||||
|
id: *id,
|
||||||
|
parent: *parent,
|
||||||
|
state: ActivityState::default(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
108 => {
|
||||||
|
// TODO: Haven't seen any of these.
|
||||||
|
warn!("Found Subtitute: {}", serde_json::to_string(&message)?);
|
||||||
|
entry.insert(Activity::Substitute(ActivitySubstitute {
|
||||||
|
id: *id,
|
||||||
|
parent: *parent,
|
||||||
|
state: ActivityState::default(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
109 => {
|
||||||
|
// TODO: Haven't seen any of these.
|
||||||
|
warn!("Found QueryPathInfo: {}", serde_json::to_string(&message)?);
|
||||||
|
entry.insert(Activity::QueryPathInfo(ActivityQueryPathInfo {
|
||||||
|
id: *id,
|
||||||
|
parent: *parent,
|
||||||
|
state: ActivityState::default(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
110 => {
|
||||||
|
// TODO: Haven't seen any of these.
|
||||||
|
warn!("Found PostBuildHook: {}", serde_json::to_string(&message)?);
|
||||||
|
entry.insert(Activity::PostBuildHook(ActivityPostBuildHook {
|
||||||
|
id: *id,
|
||||||
|
parent: *parent,
|
||||||
|
state: ActivityState::default(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
111 => {
|
||||||
|
// TODO: Haven't seen any of these.
|
||||||
|
warn!("Found BuildWaiting: {}", serde_json::to_string(&message)?);
|
||||||
|
entry.insert(Activity::BuildWaiting(ActivityBuildWaiting {
|
||||||
|
id: *id,
|
||||||
|
parent: *parent,
|
||||||
|
state: ActivityState::default(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
112 => {
|
||||||
|
// TODO: Haven't seen any of these.
|
||||||
|
warn!("Found FetchTree: {}", serde_json::to_string(&message)?);
|
||||||
|
entry.insert(Activity::FetchTree(ActivityFetchTree {
|
||||||
|
id: *id,
|
||||||
|
parent: *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);
|
||||||
|
match entry {
|
||||||
|
std::collections::btree_map::Entry::Vacant(_vacant_entry) => {
|
||||||
|
panic!("Stopped an activity that is not in the tree: {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 { id, fields, r#type } => {
|
||||||
|
match r#type {
|
||||||
|
100 => {
|
||||||
|
// FileLinked
|
||||||
|
// TODO: Haven't seen any of these.
|
||||||
|
warn!("Found FileLinked: {}", serde_json::to_string(&message)?);
|
||||||
|
}
|
||||||
|
101 => {
|
||||||
|
// BuildLogLine
|
||||||
|
// The first field is a string containing the log line
|
||||||
|
}
|
||||||
|
102 => {
|
||||||
|
// UntrustedPath
|
||||||
|
// TODO: Haven't seen any of these.
|
||||||
|
warn!("Found UntrustedPath: {}", serde_json::to_string(&message)?);
|
||||||
|
}
|
||||||
|
103 => {
|
||||||
|
// CorruptedPath
|
||||||
|
// TODO: Haven't seen any of these.
|
||||||
|
warn!("Found CorruptedPath: {}", serde_json::to_string(&message)?);
|
||||||
|
}
|
||||||
|
104 => {
|
||||||
|
// SetPhase
|
||||||
|
// The first field is the phase name
|
||||||
|
}
|
||||||
|
105 => {
|
||||||
|
// Progress
|
||||||
|
// Fields numerator, denominator, running?, failed?
|
||||||
|
}
|
||||||
|
106 => {
|
||||||
|
// SetExpected
|
||||||
|
// Fields activity type?, expected?
|
||||||
|
}
|
||||||
|
107 => {
|
||||||
|
// PostBuildLogLine
|
||||||
|
// TODO: Haven't seen any of these.
|
||||||
|
warn!(
|
||||||
|
"Found PostBuildLogLine: {}",
|
||||||
|
serde_json::to_string(&message)?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
108 => {
|
||||||
|
// FetchStatus
|
||||||
|
// TODO: Haven't seen any of these.
|
||||||
|
warn!("Found FetchStatus: {}", serde_json::to_string(&message)?);
|
||||||
|
// println!("{}", serde_json::to_string(&message)?);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("Unhandled result: {}", serde_json::to_string(&message)?);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(", ");
|
||||||
|
// println!("In progress: {name_list}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 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) => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ActivityUnknown {
|
||||||
|
id: u64,
|
||||||
|
parent: u64,
|
||||||
|
state: ActivityState,
|
||||||
|
level: u8,
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
struct ActivityCopyPath {
|
||||||
|
id: u64,
|
||||||
|
parent: u64,
|
||||||
|
state: ActivityState,
|
||||||
|
}
|
||||||
|
struct ActivityFileTransfer {
|
||||||
|
id: u64,
|
||||||
|
parent: u64,
|
||||||
|
state: ActivityState,
|
||||||
|
level: u8,
|
||||||
|
text: String,
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
struct ActivityRealize {
|
||||||
|
id: u64,
|
||||||
|
parent: u64,
|
||||||
|
state: ActivityState,
|
||||||
|
level: u8,
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
struct ActivityCopyPaths {
|
||||||
|
id: u64,
|
||||||
|
parent: u64,
|
||||||
|
state: ActivityState,
|
||||||
|
level: u8,
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
parent: u64,
|
||||||
|
state: ActivityState,
|
||||||
|
}
|
||||||
|
struct ActivityFetchTree {
|
||||||
|
id: u64,
|
||||||
|
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."),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user