From ebdadac72c35217ddb790ef41b54ebf9c43cd449 Mon Sep 17 00:00:00 2001 From: Judson Date: Wed, 13 May 2020 17:47:39 -0700 Subject: [PATCH 01/11] feat(varlink): stabilize `lorri internal stream-events` At long last, stabilizes the event stream Varlink interface. Per discussion, this formalizes most of the varlink protocol for Lorri as private, exposing only the event stream interfaces. Events are set up as closer to a proper sum type, such that each kind has a separate sub-kind to describe it. A follow up PR would move event-stream out from under the internal command and thus publish it. --- build.rs | 9 +- release.nix | 6 + src/cli.rs | 3 + src/com.target.lorri.internal.varlink | 32 ++ src/com.target.lorri.varlink | 58 ++-- src/com_target_lorri.rs | 87 ++---- src/com_target_lorri_internal.rs | 249 ++++++++++++++++ src/daemon.rs | 6 +- src/daemon/internal_proto.rs | 415 ++++++++++++++++++++++++++ src/daemon/rpc.rs | 378 ----------------------- src/lib.rs | 33 +- src/main.rs | 2 +- src/ops/direnv/mod.rs | 31 +- src/ops/mod.rs | 4 +- src/ops/ping.rs | 19 +- src/ops/stream_events.rs | 8 +- src/socket.rs | 6 + tests/daemon/main.rs | 13 +- 18 files changed, 845 insertions(+), 514 deletions(-) create mode 100644 src/com.target.lorri.internal.varlink create mode 100644 src/com_target_lorri_internal.rs create mode 100644 src/daemon/internal_proto.rs delete mode 100644 src/daemon/rpc.rs diff --git a/build.rs b/build.rs index 1176a7db..ac5b3862 100644 --- a/build.rs +++ b/build.rs @@ -42,9 +42,10 @@ pub const RUN_TIME_CLOSURE: &str = "{runtime_closure}"; ) .unwrap(); - // Generate src/com_target_lorri.rs - varlink_generator::cargo_build_tosource( + for v in &[ "src/com.target.lorri.varlink", - /* rustfmt */ true, - ); + "src/com.target.lorri.internal.varlink", + ] { + varlink_generator::cargo_build_tosource(v, /*rustfmt */ true); + } } diff --git a/release.nix b/release.nix index 9b0db8d4..e9d0973e 100644 --- a/release.nix +++ b/release.nix @@ -5,6 +5,12 @@ changelog = { # Find the current version number with `git log --pretty=%h | wc -l` entries = [ + { + version = 630; + changes = '' + Make the event stream Varlink events public, with sum-style types. + ''; + } { version = 626; changes = '' diff --git a/src/cli.rs b/src/cli.rs index 1b87850b..79e302d7 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -171,6 +171,9 @@ pub struct Ping_ { /// The .nix file to watch and build on changes. #[structopt(parse(from_os_str))] pub nix_file: PathBuf, + /// A custom socket address to ping - used mostly for testing and experiments + #[structopt(long = "socket-address")] + pub socket_address: Option, } /// Stream events from the daemon. diff --git a/src/com.target.lorri.internal.varlink b/src/com.target.lorri.internal.varlink new file mode 100644 index 00000000..1be318c6 --- /dev/null +++ b/src/com.target.lorri.internal.varlink @@ -0,0 +1,32 @@ +# The interface `lorri daemon` exposes. +interface com.target.lorri.internal + +# WatchShell instructs the daemon to evaluate a Nix expression and re-evaluate +# it when it or its dependencies change. +method WatchShell(shell_nix: ShellNix) -> () + +# ShellNix describes the Nix expression which evaluates to a development +# environment. +type ShellNix ( + # The absolute path of a Nix file specifying the project environment. + path: string +) + +type Reason ( + kind: (project_added, ping_received, files_changed, unknown), + project: ?ShellNix, # only present if kind == project_added + files: ?[]string, # only present if kind == files_changed + debug: ?string # only present if kind == unknown +) + +type Outcome ( + project_root: string +) + +type Failure ( + kind: (io, spawn, exit, output), + msg: ?string, # only present if kind in (io, spawn) + cmd: ?string, # only present if kind in (spawn, exit) + status: ?int, # only present if kind == exit + logs: ?[]string # only present if kind == exit +) diff --git a/src/com.target.lorri.varlink b/src/com.target.lorri.varlink index 47b28e16..91530b25 100644 --- a/src/com.target.lorri.varlink +++ b/src/com.target.lorri.varlink @@ -1,46 +1,58 @@ # The interface `lorri daemon` exposes. interface com.target.lorri -# WatchShell instructs the daemon to evaluate a Nix expression and re-evaluate -# it when it or its dependencies change. -method WatchShell(shell_nix: ShellNix) -> () - -# ShellNix describes the Nix expression which evaluates to a development -# environment. -type ShellNix ( - # The absolute path of a Nix file specifying the project environment. - path: string -) - # Monitor the daemon. The method will reply with an update whenever a build begins or ends. -# Montior will immediately reply with a snapshot of known projects, then a marker event, +# Monitor will immediately reply with a snapshot of known projects, then a marker event, # indicating that the stream of events is now "live." method Monitor() -> (event: Event) type Event ( kind: (section_end, started, completed, failure), - nix_file: ?ShellNix, # only present if kind != section_end - reason: ?Reason, # only present if kind == started - result: ?Outcome, # only present if kind == completed - failure: ?Failure # only present if kind == failure + section: ?SectionMarker, # present iff kind == section_end + reason: ?Reason, # present iff kind == started + result: ?Outcome, # present iff kind == completed + failure: ?Failure # present iff kind == failure ) + +type SectionMarker () + type Reason ( kind: (project_added, ping_received, files_changed, unknown), - project: ?ShellNix, # only present if kind == project_added - files: ?[]string, # only present if kind == files_changed - debug: ?string # only present if kind == unknown + project: ?string, # present iff kind == project_added + files: ?[]string, # present iff kind == files_changed + debug: ?string # present iff kind == unknown ) type Outcome ( + nix_file: ?string, project_root: string ) type Failure ( kind: (io, spawn, exit, output), - msg: ?string, # only present if kind in (io, spawn) - cmd: ?string, # only present if kind in (spawn, exit) - status: ?int, # only present if kind == exit - logs: ?[]string # only present if kind == exit + nix_file: string, + io: ?IOFail, # present iff kind == io + spawn: ?SpawnFail, # present iff kind == spawn + exit: ?ExitFail, # present iff kind == exit + output: ?OutputFail # present iff kind == output +) + +type IOFail ( + msg: string +) + +type SpawnFail ( + msg: string, + cmd: string ) +type ExitFail ( + cmd: string, + status: ?int, + logs: []string +) + +type OutputFail ( + msg: string +) diff --git a/src/com_target_lorri.rs b/src/com_target_lorri.rs index 7004815b..7e121037 100644 --- a/src/com_target_lorri.rs +++ b/src/com_target_lorri.rs @@ -118,12 +118,18 @@ pub enum r#Event_kind { #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct r#Event { pub r#kind: Event_kind, - pub r#nix_file: Option, + pub r#section: Option, pub r#reason: Option, pub r#result: Option, pub r#failure: Option, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#ExitFail { + pub r#cmd: String, + pub r#status: Option, + pub r#logs: Vec, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub enum r#Failure_kind { r#io, r#spawn, @@ -133,16 +139,26 @@ pub enum r#Failure_kind { #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct r#Failure { pub r#kind: Failure_kind, - pub r#msg: Option, - pub r#cmd: Option, - pub r#status: Option, - pub r#logs: Option>, + pub r#nix_file: String, + pub r#io: Option, + pub r#spawn: Option, + pub r#exit: Option, + pub r#output: Option, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#IOFail { + pub r#msg: String, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct r#Outcome { + pub r#nix_file: Option, pub r#project_root: String, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#OutputFail { + pub r#msg: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub enum r#Reason_kind { r#project_added, r#ping_received, @@ -152,13 +168,16 @@ pub enum r#Reason_kind { #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct r#Reason { pub r#kind: Reason_kind, - pub r#project: Option, + pub r#project: Option, pub r#files: Option>, pub r#debug: Option, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -pub struct r#ShellNix { - pub r#path: String, +pub struct r#SectionMarker {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#SpawnFail { + pub r#msg: String, + pub r#cmd: String, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct Monitor_Reply { @@ -173,26 +192,8 @@ pub trait Call_Monitor: VarlinkCallError { } } impl<'a> Call_Monitor for varlink::Call<'a> {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -pub struct WatchShell_Reply {} -impl varlink::VarlinkReply for WatchShell_Reply {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -pub struct WatchShell_Args { - pub r#shell_nix: ShellNix, -} -pub trait Call_WatchShell: VarlinkCallError { - fn reply(&mut self) -> varlink::Result<()> { - self.reply_struct(varlink::Reply::parameters(None)) - } -} -impl<'a> Call_WatchShell for varlink::Call<'a> {} pub trait VarlinkInterface { fn monitor(&self, call: &mut dyn Call_Monitor) -> varlink::Result<()>; - fn watch_shell( - &self, - call: &mut dyn Call_WatchShell, - r#shell_nix: ShellNix, - ) -> varlink::Result<()>; fn call_upgraded( &self, _call: &mut varlink::Call, @@ -203,10 +204,6 @@ pub trait VarlinkInterface { } pub trait VarlinkClientInterface { fn monitor(&mut self) -> varlink::MethodCall; - fn watch_shell( - &mut self, - r#shell_nix: ShellNix, - ) -> varlink::MethodCall; } #[allow(dead_code)] pub struct VarlinkClient { @@ -226,16 +223,6 @@ impl VarlinkClientInterface for VarlinkClient { Monitor_Args {}, ) } - fn watch_shell( - &mut self, - r#shell_nix: ShellNix, - ) -> varlink::MethodCall { - varlink::MethodCall::::new( - self.connection.clone(), - "com.target.lorri.WatchShell", - WatchShell_Args { r#shell_nix }, - ) - } } #[allow(dead_code)] pub struct VarlinkInterfaceProxy { @@ -247,7 +234,7 @@ pub fn new(inner: Box) -> VarlinkInterfacePr } impl varlink::Interface for VarlinkInterfaceProxy { fn get_description(&self) -> &'static str { - "# The interface `lorri daemon` exposes.\ninterface com.target.lorri\n\n# WatchShell instructs the daemon to evaluate a Nix expression and re-evaluate\n# it when it or its dependencies change.\nmethod WatchShell(shell_nix: ShellNix) -> ()\n\n# ShellNix describes the Nix expression which evaluates to a development\n# environment.\ntype ShellNix (\n # The absolute path of a Nix file specifying the project environment.\n path: string\n)\n\n# Monitor the daemon. The method will reply with an update whenever a build begins or ends.\n# Montior will immediately reply with a snapshot of known projects, then a marker event,\n# indicating that the stream of events is now \"live.\"\nmethod Monitor() -> (event: Event)\n\ntype Event (\n kind: (section_end, started, completed, failure),\n nix_file: ?ShellNix, # only present if kind != section_end\n reason: ?Reason, # only present if kind == started\n result: ?Outcome, # only present if kind == completed\n failure: ?Failure # only present if kind == failure\n)\n\ntype Reason (\n kind: (project_added, ping_received, files_changed, unknown),\n project: ?ShellNix, # only present if kind == project_added\n files: ?[]string, # only present if kind == files_changed\n debug: ?string # only present if kind == unknown\n)\n\ntype Outcome (\n project_root: string\n)\n\ntype Failure (\n kind: (io, spawn, exit, output),\n msg: ?string, # only present if kind in (io, spawn)\n cmd: ?string, # only present if kind in (spawn, exit)\n status: ?int, # only present if kind == exit\n logs: ?[]string # only present if kind == exit\n)\n\n" + "# The interface `lorri daemon` exposes.\ninterface com.target.lorri\n\n# Monitor the daemon. The method will reply with an update whenever a build begins or ends.\n# Monitor will immediately reply with a snapshot of known projects, then a marker event,\n# indicating that the stream of events is now \"live.\"\nmethod Monitor() -> (event: Event)\n\ntype Event (\n kind: (section_end, started, completed, failure),\n section: ?SectionMarker, # present iff kind == section_end\n reason: ?Reason, # present iff kind == started\n result: ?Outcome, # present iff kind == completed\n failure: ?Failure # present iff kind == failure\n)\n\n\ntype SectionMarker ()\n\ntype Reason (\n kind: (project_added, ping_received, files_changed, unknown),\n project: ?string, # present iff kind == project_added\n files: ?[]string, # present iff kind == files_changed\n debug: ?string # present iff kind == unknown\n)\n\ntype Outcome (\n nix_file: ?string,\n project_root: string\n)\n\ntype Failure (\n kind: (io, spawn, exit, output),\n nix_file: string,\n io: ?IOFail, # present iff kind == io\n spawn: ?SpawnFail, # present iff kind == spawn\n exit: ?ExitFail, # present iff kind == exit\n output: ?OutputFail # present iff kind == output\n)\n\ntype IOFail (\n msg: string\n)\n\ntype SpawnFail (\n msg: string,\n cmd: string\n)\n\ntype ExitFail (\n cmd: string,\n status: ?int,\n logs: []string\n)\n\ntype OutputFail (\n msg: string\n)\n" } fn get_name(&self) -> &'static str { "com.target.lorri" @@ -263,24 +250,6 @@ impl varlink::Interface for VarlinkInterfaceProxy { let req = call.request.unwrap(); match req.method.as_ref() { "com.target.lorri.Monitor" => self.inner.monitor(call as &mut dyn Call_Monitor), - "com.target.lorri.WatchShell" => { - if let Some(args) = req.parameters.clone() { - let args: WatchShell_Args = match serde_json::from_value(args) { - Ok(v) => v, - Err(e) => { - let es = format!("{}", e); - let _ = call.reply_invalid_parameter(es.clone()); - return Err( - varlink::context!(varlink::ErrorKind::SerdeJsonDe(es)).into() - ); - } - }; - self.inner - .watch_shell(call as &mut dyn Call_WatchShell, args.r#shell_nix) - } else { - call.reply_invalid_parameter("parameters".into()) - } - } m => call.reply_method_not_found(String::from(m)), } } diff --git a/src/com_target_lorri_internal.rs b/src/com_target_lorri_internal.rs new file mode 100644 index 00000000..ace87947 --- /dev/null +++ b/src/com_target_lorri_internal.rs @@ -0,0 +1,249 @@ +#![doc = "This file was automatically generated by the varlink rust generator"] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +use serde_derive::{Deserialize, Serialize}; +use serde_json; +use std::io::BufRead; +use std::sync::{Arc, RwLock}; +use varlink::{self, CallTrait}; +#[allow(dead_code)] +#[derive(Clone, PartialEq, Debug)] +pub enum ErrorKind { + Varlink_Error, + VarlinkReply_Error, +} +impl ::std::fmt::Display for ErrorKind { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match self { + ErrorKind::Varlink_Error => write!(f, "Varlink Error"), + ErrorKind::VarlinkReply_Error => write!(f, "Varlink error reply"), + } + } +} +pub struct Error( + pub ErrorKind, + pub Option>, + pub Option<&'static str>, +); +impl Error { + #[allow(dead_code)] + pub fn kind(&self) -> &ErrorKind { + &self.0 + } +} +impl From for Error { + fn from(e: ErrorKind) -> Self { + Error(e, None, None) + } +} +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.1 + .as_ref() + .map(|e| e.as_ref() as &(dyn std::error::Error + 'static)) + } +} +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} +impl std::fmt::Debug for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + use std::error::Error as StdError; + if let Some(ref o) = self.2 { + std::fmt::Display::fmt(o, f)?; + } + std::fmt::Debug::fmt(&self.0, f)?; + if let Some(e) = self.source() { + std::fmt::Display::fmt("\nCaused by:\n", f)?; + std::fmt::Debug::fmt(&e, f)?; + } + Ok(()) + } +} +#[allow(dead_code)] +pub type Result = std::result::Result; +impl From for Error { + fn from(e: varlink::Error) -> Self { + match e.kind() { + varlink::ErrorKind::VarlinkErrorReply(r) => Error( + ErrorKind::from(r), + Some(Box::from(e)), + Some(concat!(file!(), ":", line!(), ": ")), + ), + _ => Error( + ErrorKind::Varlink_Error, + Some(Box::from(e)), + Some(concat!(file!(), ":", line!(), ": ")), + ), + } + } +} +#[allow(dead_code)] +impl Error { + pub fn source_varlink_kind(&self) -> Option<&varlink::ErrorKind> { + use std::error::Error as StdError; + let mut s: &dyn StdError = self; + while let Some(c) = s.source() { + let k = self + .source() + .and_then(|e| e.downcast_ref::()) + .and_then(|e| Some(e.kind())); + if k.is_some() { + return k; + } + s = c; + } + None + } +} +impl From<&varlink::Reply> for ErrorKind { + #[allow(unused_variables)] + fn from(e: &varlink::Reply) -> Self { + match e { + _ => ErrorKind::VarlinkReply_Error, + } + } +} +pub trait VarlinkCallError: varlink::CallTrait {} +impl<'a> VarlinkCallError for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum r#Failure_kind { + r#io, + r#spawn, + r#exit, + r#output, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#Failure { + pub r#kind: Failure_kind, + pub r#msg: Option, + pub r#cmd: Option, + pub r#status: Option, + pub r#logs: Option>, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#Outcome { + pub r#project_root: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum r#Reason_kind { + r#project_added, + r#ping_received, + r#files_changed, + r#unknown, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#Reason { + pub r#kind: Reason_kind, + pub r#project: Option, + pub r#files: Option>, + pub r#debug: Option, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#ShellNix { + pub r#path: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct WatchShell_Reply {} +impl varlink::VarlinkReply for WatchShell_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct WatchShell_Args { + pub r#shell_nix: ShellNix, +} +pub trait Call_WatchShell: VarlinkCallError { + fn reply(&mut self) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::parameters(None)) + } +} +impl<'a> Call_WatchShell for varlink::Call<'a> {} +pub trait VarlinkInterface { + fn watch_shell( + &self, + call: &mut dyn Call_WatchShell, + r#shell_nix: ShellNix, + ) -> varlink::Result<()>; + fn call_upgraded( + &self, + _call: &mut varlink::Call, + _bufreader: &mut dyn BufRead, + ) -> varlink::Result> { + Ok(Vec::new()) + } +} +pub trait VarlinkClientInterface { + fn watch_shell( + &mut self, + r#shell_nix: ShellNix, + ) -> varlink::MethodCall; +} +#[allow(dead_code)] +pub struct VarlinkClient { + connection: Arc>, +} +impl VarlinkClient { + #[allow(dead_code)] + pub fn new(connection: Arc>) -> Self { + VarlinkClient { connection } + } +} +impl VarlinkClientInterface for VarlinkClient { + fn watch_shell( + &mut self, + r#shell_nix: ShellNix, + ) -> varlink::MethodCall { + varlink::MethodCall::::new( + self.connection.clone(), + "com.target.lorri.internal.WatchShell", + WatchShell_Args { r#shell_nix }, + ) + } +} +#[allow(dead_code)] +pub struct VarlinkInterfaceProxy { + inner: Box, +} +#[allow(dead_code)] +pub fn new(inner: Box) -> VarlinkInterfaceProxy { + VarlinkInterfaceProxy { inner } +} +impl varlink::Interface for VarlinkInterfaceProxy { + fn get_description(&self) -> &'static str { + "# The interface `lorri daemon` exposes.\ninterface com.target.lorri.internal\n\n# WatchShell instructs the daemon to evaluate a Nix expression and re-evaluate\n# it when it or its dependencies change.\nmethod WatchShell(shell_nix: ShellNix) -> ()\n\n# ShellNix describes the Nix expression which evaluates to a development\n# environment.\ntype ShellNix (\n # The absolute path of a Nix file specifying the project environment.\n path: string\n)\n\ntype Reason (\n kind: (project_added, ping_received, files_changed, unknown),\n project: ?ShellNix, # only present if kind == project_added\n files: ?[]string, # only present if kind == files_changed\n debug: ?string # only present if kind == unknown\n)\n\ntype Outcome (\n project_root: string\n)\n\ntype Failure (\n kind: (io, spawn, exit, output),\n msg: ?string, # only present if kind in (io, spawn)\n cmd: ?string, # only present if kind in (spawn, exit)\n status: ?int, # only present if kind == exit\n logs: ?[]string # only present if kind == exit\n)\n" + } + fn get_name(&self) -> &'static str { + "com.target.lorri.internal" + } + fn call_upgraded( + &self, + call: &mut varlink::Call, + bufreader: &mut dyn BufRead, + ) -> varlink::Result> { + self.inner.call_upgraded(call, bufreader) + } + fn call(&self, call: &mut varlink::Call) -> varlink::Result<()> { + let req = call.request.unwrap(); + match req.method.as_ref() { + "com.target.lorri.internal.WatchShell" => { + if let Some(args) = req.parameters.clone() { + let args: WatchShell_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err( + varlink::context!(varlink::ErrorKind::SerdeJsonDe(es)).into() + ); + } + }; + self.inner + .watch_shell(call as &mut dyn Call_WatchShell, args.r#shell_nix) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + m => call.reply_method_not_found(String::from(m)), + } + } +} diff --git a/src/daemon.rs b/src/daemon.rs index 56039067..a8db811e 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -10,7 +10,7 @@ use slog_scope::debug; use std::collections::HashMap; use std::path::PathBuf; -mod rpc; +mod internal_proto; #[derive(Debug, Clone)] /// Union of build_loop::Event and NewListener for internal use. @@ -92,8 +92,8 @@ impl Daemon { let mut pool = crate::thread::Pool::new(); let build_events_tx = self.build_events_tx.clone(); - let server = - rpc::Server::new(socket_path.clone(), activity_tx, build_events_tx).map_err(|e| { + let server = internal_proto::Server::new(socket_path.clone(), activity_tx, build_events_tx) + .map_err(|e| { ExitError::temporary(format!( "unable to bind to the server socket at {}: {:?}", socket_path.0.display(), diff --git a/src/daemon/internal_proto.rs b/src/daemon/internal_proto.rs new file mode 100644 index 00000000..90953cf2 --- /dev/null +++ b/src/daemon/internal_proto.rs @@ -0,0 +1,415 @@ +//! The daemon's RPC server. + +use super::IndicateActivity; +use super::LoopHandlerEvent; +use crate::build_loop; +use crate::error; +use crate::internal_proto; +use crate::ops::error::ExitError; +use crate::proto; +use crate::socket::{BindLock, SocketPath}; +use crate::watch; +use crate::NixFile; + +use crossbeam_channel as chan; +use slog_scope::debug; +use std::convert::{TryFrom, TryInto}; + +/// The daemon server. +pub struct Server { + activity_tx: chan::Sender, + build_tx: chan::Sender, + socket_path: SocketPath, + _lock: BindLock, +} + +impl Server { + /// Create a new Server. Locks the Unix socket path, so there can be only one Server instance + /// per socket path at any time. + pub fn new( + socket_path: SocketPath, + activity_tx: chan::Sender, + build_tx: chan::Sender, + ) -> Result { + let lock = socket_path.lock()?; + Ok(Server { + socket_path, + activity_tx, + build_tx, + _lock: lock, + }) + } + + /// Serve the daemon endpoint. + pub fn serve(self) -> Result<(), ExitError> { + let address = &self.socket_path.address(); + let service = varlink::VarlinkService::new( + /* vendor */ "com.target", + /* product */ "lorri", + /* version */ "0.1", + /* url */ "https://github.com/target/lorri", + vec![Box::new(internal_proto::new(Box::new(self)))], + ); + let initial_worker_threads = 1; + let max_worker_threads = 10; + let idle_timeout = 0; + varlink::listen( + service, + address, + initial_worker_threads, + max_worker_threads, + idle_timeout, + ) + .map_err(|e| ExitError::temporary(format!("{}", e))) + } +} + +/// The actual varlink server implementation. See com.target.lorri.varlink for the interface +/// specification. +impl internal_proto::VarlinkInterface for Server { + fn watch_shell( + &self, + call: &mut dyn internal_proto::Call_WatchShell, + shell_nix: internal_proto::ShellNix, + ) -> varlink::Result<()> { + let p = std::path::PathBuf::from(&shell_nix.path); + if p.is_file() { + self.activity_tx + .send(IndicateActivity { + nix_file: NixFile::from(p), + }) + .expect("failed to indicate activity via channel"); + call.reply() + } else { + call.reply_invalid_parameter(format!("{:?}", shell_nix)) + } + } +} + +impl proto::VarlinkInterface for Server { + fn monitor(&self, call: &mut dyn proto::Call_Monitor) -> varlink::Result<()> { + if !call.wants_more() { + return call.reply_invalid_parameter("wants_more".into()); + } + + let (tx, rx) = chan::unbounded(); + self.build_tx + .send(LoopHandlerEvent::NewListener(tx)) + .map_err(|_| varlink::error::ErrorKind::Server)?; + + call.set_continues(true); + for event in rx { + debug!("event for varlink"; "event" => ?&event); + match event.try_into() { + Ok(ev) => call.reply(ev), + Err(e) => call.reply_invalid_parameter(e.to_string()), + }?; + } + Ok(()) + } +} + +impl TryFrom<&build_loop::Event> for proto::Event { + type Error = String; + + fn try_from(ev: &build_loop::Event) -> Result { + use build_loop::Event; + use proto::Event_kind as kind; + Ok(match ev { + Event::SectionEnd => proto::Event { + kind: kind::section_end, + section: Some(proto::SectionMarker {}), + reason: None, + result: None, + failure: None, + }, + Event::Started { reason, .. } => proto::Event { + kind: kind::started, + section: None, + reason: Some(reason.try_into()?), + result: None, + failure: None, + }, + Event::Completed { .. } => proto::Event { + kind: kind::completed, + section: None, + reason: None, + result: Some(ev.try_into()?), + failure: None, + }, + Event::Failure { .. } => proto::Event { + kind: kind::failure, + section: None, + reason: None, + result: None, + failure: Some(ev.try_into()?), + }, + }) + } +} + +impl TryFrom for proto::Event { + type Error = String; + + fn try_from(ev: build_loop::Event) -> Result { + proto::Event::try_from(&ev) + } +} + +impl TryFrom for build_loop::Event { + type Error = String; + + fn try_from(mr: proto::Monitor_Reply) -> Result { + build_loop::Event::try_from(mr.event) + } +} + +impl TryFrom for build_loop::Event { + type Error = String; + + fn try_from(re: proto::Event) -> Result { + use proto::Event_kind::*; + + Ok(match re.kind { + section_end => build_loop::Event::SectionEnd, + started => re.reason.ok_or("missing reason")?.try_into()?, + completed => re.result.ok_or("missing result")?.try_into()?, + failure => re.failure.ok_or("missing failure log")?.try_into()?, + }) + } +} + +impl TryFrom for build_loop::Event { + type Error = String; + + fn try_from(r: proto::Reason) -> Result { + Ok(build_loop::Event::Started { + nix_file: r.project.clone().ok_or("missing nix file!")?.into(), + reason: r.try_into()?, + }) + } +} + +impl TryFrom<&watch::Reason> for proto::Reason { + type Error = &'static str; + + fn try_from(wr: &watch::Reason) -> Result { + use proto::Reason_kind::*; + use watch::Reason; + + Ok(match wr { + Reason::PingReceived => proto::Reason { + kind: ping_received, + project: None, + files: None, + debug: None, + }, + Reason::ProjectAdded(nix_file) => proto::Reason { + kind: project_added, + project: Some(nix_file.to_string()), + files: None, + debug: None, + }, + Reason::FilesChanged(changed) => proto::Reason { + kind: files_changed, + project: None, + files: Some( + changed + .iter() + .map(|pb| Ok(pb.to_str().ok_or("cannot convert path!")?.to_string())) + .collect::, &'static str>>()?, + ), + debug: None, + }, + Reason::UnknownEvent(dbg) => proto::Reason { + kind: unknown, + project: None, + files: None, + debug: Some(dbg.into()), + }, + }) + } +} + +impl TryFrom for watch::Reason { + type Error = String; + + fn try_from(rr: proto::Reason) -> Result { + use proto::Reason_kind::*; + use watch::Reason; + + Ok(match rr.kind { + ping_received => Reason::PingReceived, + project_added => Reason::ProjectAdded(rr.project.ok_or("missing nix file!")?.into()), + files_changed => Reason::FilesChanged( + rr.files + .ok_or("missing files!")? + .into_iter() + .map(|s| s.into()) + .collect(), + ), + unknown => Reason::UnknownEvent(rr.debug.ok_or("missing debug string!")?.into()), + }) + } +} + +impl TryFrom<&build_loop::Event> for proto::Outcome { + type Error = String; + + fn try_from(ev: &build_loop::Event) -> Result { + if let build_loop::Event::Completed { nix_file, result } = ev { + Ok(proto::Outcome { + nix_file: Some(nix_file.to_string()), + project_root: result.output_paths.shell_gc_root.to_string(), + }) + } else { + Err(format!("can't make an Outcome out of {:?}", ev)) + } + } +} + +impl TryFrom for build_loop::Event { + type Error = String; + + fn try_from(o: proto::Outcome) -> Result { + Ok(build_loop::Event::Completed { + nix_file: o.nix_file.clone().ok_or("missing nix file!")?.into(), + result: o.into(), + }) + } +} + +impl From for build_loop::BuildResults { + fn from(ro: proto::Outcome) -> Self { + use crate::build_loop::BuildResults; + use crate::builder::OutputPaths; + use crate::project::roots::RootPath; + + BuildResults { + output_paths: OutputPaths { + shell_gc_root: RootPath(ro.project_root.into()), + }, + } + } +} + +impl TryFrom<&build_loop::Event> for proto::Failure { + type Error = &'static str; + + fn try_from(ev: &build_loop::Event) -> Result { + use error::BuildError; + use proto::Failure_kind::*; + + match ev { + build_loop::Event::Failure { nix_file, failure } => Ok(match failure { + BuildError::Io { msg } => proto::Failure { + kind: io, + nix_file: nix_file.to_string(), + io: Some(proto::IOFail { msg: msg.clone() }), + spawn: None, + exit: None, + output: None, + }, + BuildError::Spawn { cmd, msg } => proto::Failure { + kind: spawn, + nix_file: nix_file.to_string(), + io: None, + spawn: Some(proto::SpawnFail { + cmd: cmd.clone(), + msg: msg.clone(), + }), + exit: None, + output: None, + }, + BuildError::Exit { cmd, status, logs } => proto::Failure { + kind: exit, + nix_file: nix_file.to_string(), + io: None, + spawn: None, + exit: Some(proto::ExitFail { + cmd: cmd.clone(), + status: status.map(|i| i as i64), + logs: logs.iter().map(|l| l.to_string()).collect(), + }), + output: None, + }, + BuildError::Output { msg } => proto::Failure { + kind: output, + nix_file: nix_file.to_string(), + io: None, + spawn: None, + exit: None, + output: Some(proto::OutputFail { msg: msg.clone() }), + }, + }), + _ => Err("expecting build_loop::Event::Failure"), + } + } +} + +impl TryFrom for build_loop::Event { + type Error = String; + + fn try_from(f: proto::Failure) -> Result { + Ok(build_loop::Event::Failure { + nix_file: f.nix_file.clone().into(), + failure: f.try_into()?, + }) + } +} + +impl TryFrom for error::BuildError { + type Error = &'static str; + + fn try_from(pf: proto::Failure) -> Result { + use error::BuildError; + use proto::Failure_kind::*; + + match pf { + proto::Failure { + kind: io, + io: Some(proto::IOFail { msg }), + .. + } => Ok(BuildError::Io { msg }), + proto::Failure { + kind: spawn, + spawn: Some(proto::SpawnFail { cmd, msg }), + .. + } => Ok(BuildError::Spawn { cmd, msg }), + proto::Failure { + kind: exit, + exit: Some(proto::ExitFail { cmd, status, logs }), + .. + } => Ok(BuildError::Exit { + cmd, + status: status.map(|i| i as i32), + logs: logs.iter().map(|l| l.clone().into()).collect(), + }), + proto::Failure { + kind: output, + output: Some(proto::OutputFail { msg }), + .. + } => Ok(BuildError::Output { msg }), + _ => Err("unexpected form of proto::Failure"), + } + } +} + +impl TryFrom<&NixFile> for internal_proto::ShellNix { + type Error = &'static str; + + fn try_from(nix_file: &NixFile) -> Result { + match nix_file.as_path().as_os_str().to_str() { + Some(s) => Ok(internal_proto::ShellNix { + path: s.to_string(), + }), + None => Err("nix file path is not UTF-8 clean"), + } + } +} + +impl From for NixFile { + fn from(shell_nix: internal_proto::ShellNix) -> Self { + Self::from(shell_nix.path) + } +} diff --git a/src/daemon/rpc.rs b/src/daemon/rpc.rs deleted file mode 100644 index f00e9817..00000000 --- a/src/daemon/rpc.rs +++ /dev/null @@ -1,378 +0,0 @@ -//! The daemon's RPC server. - -use super::IndicateActivity; -use super::LoopHandlerEvent; -use crate::build_loop::Event; -use crate::error; -use crate::rpc; -use crate::socket::{BindLock, SocketPath}; -use crate::watch; -use crate::NixFile; -use crossbeam_channel as chan; -use slog_scope::debug; -use std::convert::TryFrom; -use std::path::PathBuf; - -/// The daemon server. -pub struct Server { - activity_tx: chan::Sender, - build_tx: chan::Sender, - socket_path: SocketPath, - _lock: BindLock, -} - -impl Server { - /// Create a new Server. Locks the Unix socket path, so there can be only one Server instance - /// per socket path at any time. - pub fn new( - socket_path: SocketPath, - activity_tx: chan::Sender, - build_tx: chan::Sender, - ) -> Result { - let lock = socket_path.lock()?; - Ok(Server { - socket_path, - activity_tx, - build_tx, - _lock: lock, - }) - } - - /// Serve the daemon endpoint. - pub fn serve(self) -> Result<(), varlink::error::Error> { - let address = &self.socket_path.address(); - let service = varlink::VarlinkService::new( - /* vendor */ "com.target", - /* product */ "lorri", - /* version */ "0.1", - /* url */ "https://github.com/target/lorri", - vec![Box::new(rpc::new(Box::new(self)))], - ); - let initial_worker_threads = 1; - let max_worker_threads = 10; - let idle_timeout = 0; - varlink::listen( - service, - address, - initial_worker_threads, - max_worker_threads, - idle_timeout, - ) - } -} - -/// The actual varlink server implementation. See com.target.lorri.varlink for the interface -/// specification. -impl rpc::VarlinkInterface for Server { - fn watch_shell( - &self, - call: &mut dyn rpc::Call_WatchShell, - shell_nix: rpc::ShellNix, - ) -> varlink::Result<()> { - match NixFile::try_from(shell_nix) { - Ok(nix_file) => { - self.activity_tx - .send(IndicateActivity { nix_file }) - .expect("failed to indicate activity via channel"); - call.reply() - } - Err(e) => call.reply_invalid_parameter(e), - } - } - - fn monitor(&self, call: &mut dyn rpc::Call_Monitor) -> varlink::Result<()> { - if !call.wants_more() { - return call.reply_invalid_parameter("wants_more".into()); - } - - let (tx, rx) = chan::unbounded(); - self.build_tx - .send(LoopHandlerEvent::NewListener(tx)) - .map_err(|_| varlink::error::ErrorKind::Server)?; - - call.set_continues(true); - for event in rx { - debug!("event for varlink"; "event" => ?&event); - match event.try_into() { - Ok(ev) => call.reply(ev), - Err(e) => call.reply_invalid_parameter(e.to_string()), - }?; - } - Ok(()) - } -} - -use std::convert::TryInto; - -impl TryFrom<&Event> for rpc::Event { - type Error = &'static str; - - fn try_from(ev: &Event) -> Result { - use rpc::Event_kind as kind; - Ok(match ev { - Event::SectionEnd => rpc::Event { - kind: kind::section_end, - nix_file: None, - reason: None, - result: None, - failure: None, - }, - Event::Started { nix_file, reason } => rpc::Event { - kind: kind::started, - nix_file: Some(nix_file.try_into()?), - reason: Some(reason.try_into()?), - result: None, - failure: None, - }, - Event::Completed { nix_file, result } => rpc::Event { - kind: kind::completed, - nix_file: Some(nix_file.try_into()?), - reason: None, - result: Some(result.try_into()?), - failure: None, - }, - Event::Failure { nix_file, failure } => rpc::Event { - kind: kind::failure, - nix_file: Some(nix_file.try_into()?), - reason: None, - result: None, - failure: Some(failure.try_into()?), - }, - }) - } -} - -impl TryFrom for rpc::Event { - type Error = &'static str; - - fn try_from(ev: Event) -> Result { - rpc::Event::try_from(&ev) - } -} - -impl TryFrom for Event { - type Error = String; - - fn try_from(mr: rpc::Monitor_Reply) -> Result { - Event::try_from(mr.event) - } -} - -impl TryFrom for Event { - type Error = String; - - fn try_from(re: rpc::Event) -> Result { - use rpc::Event_kind::*; - - Ok(match re.kind { - section_end => Event::SectionEnd, - started => Event::Started { - nix_file: re.nix_file.ok_or("missing nix file!")?.try_into()?, - reason: re.reason.ok_or("missing reason!")?.try_into()?, - }, - completed => Event::Completed { - nix_file: re.nix_file.ok_or("missing nix file!")?.try_into()?, - result: re.result.ok_or("missing result!")?.into(), - }, - failure => Event::Failure { - nix_file: re.nix_file.ok_or("missing nix file!")?.try_into()?, - failure: re.failure.ok_or("missing failure log")?.try_into()?, - }, - }) - } -} - -impl TryFrom<&watch::Reason> for rpc::Reason { - type Error = &'static str; - - fn try_from(wr: &watch::Reason) -> Result { - use rpc::Reason_kind::*; - use watch::Reason; - - Ok(match wr { - Reason::PingReceived => rpc::Reason { - kind: ping_received, - project: None, - files: None, - debug: None, - }, - Reason::ProjectAdded(project) => rpc::Reason { - kind: project_added, - project: Some(rpc::ShellNix::try_from(project)?), - files: None, - debug: None, - }, - Reason::FilesChanged(changed) => rpc::Reason { - kind: files_changed, - project: None, - files: Some( - changed - .iter() - .map(|pb| Ok(pb.to_str().ok_or("cannot convert path!")?.to_string())) - .collect::, &'static str>>()?, - ), - debug: None, - }, - Reason::UnknownEvent(dbg) => rpc::Reason { - kind: unknown, - project: None, - files: None, - debug: Some(dbg.into()), - }, - }) - } -} - -impl TryFrom for watch::Reason { - type Error = String; - - fn try_from(rr: rpc::Reason) -> Result { - use rpc::Reason_kind::*; - use watch::Reason; - - Ok(match rr.kind { - ping_received => Reason::PingReceived, - project_added => { - Reason::ProjectAdded(rr.project.ok_or("missing nix file!")?.try_into()?) - } - files_changed => Reason::FilesChanged( - rr.files - .ok_or("missing files!")? - .into_iter() - .map(|s| s.into()) - .collect(), - ), - unknown => Reason::UnknownEvent(rr.debug.ok_or("missing debug string!")?.into()), - }) - } -} - -use crate::build_loop; - -impl TryFrom<&build_loop::BuildResults> for rpc::Outcome { - type Error = &'static str; - - fn try_from(br: &build_loop::BuildResults) -> Result { - Ok(rpc::Outcome { - project_root: br - .output_paths - .shell_gc_root - .as_os_str() - .to_str() - .ok_or("cannot convert gc root to string")? - .to_string(), - }) - } -} - -impl From for build_loop::BuildResults { - fn from(ro: rpc::Outcome) -> Self { - use crate::build_loop::BuildResults; - use crate::builder::OutputPaths; - use crate::project::roots::RootPath; - - BuildResults { - output_paths: OutputPaths { - shell_gc_root: RootPath(ro.project_root.into()), - }, - } - } -} - -impl TryFrom<&error::BuildError> for rpc::Failure { - type Error = &'static str; - - fn try_from(bef: &error::BuildError) -> Result { - use error::BuildError; - use rpc::Failure_kind::*; - - Ok(match bef { - BuildError::Io { msg } => rpc::Failure { - kind: io, - msg: Some(msg.into()), - cmd: None, - logs: None, - status: None, - }, - BuildError::Spawn { cmd, msg } => rpc::Failure { - kind: spawn, - cmd: Some(cmd.into()), - msg: Some(msg.into()), - logs: None, - status: None, - }, - BuildError::Exit { cmd, status, logs } => rpc::Failure { - kind: exit, - cmd: Some(cmd.into()), - logs: Some(logs.iter().map(|line| line.to_string()).collect()), - msg: None, - status: status.map(i64::from), - }, - BuildError::Output { msg } => rpc::Failure { - kind: output, - msg: Some(msg.into()), - cmd: None, - logs: None, - status: None, - }, - }) - } -} - -impl TryFrom for error::BuildError { - type Error = &'static str; - - fn try_from(rf: rpc::Failure) -> Result { - use error::BuildError; - use rpc::Failure_kind::*; - - Ok(match rf.kind { - io => BuildError::Io { - msg: rf.msg.ok_or("io failure without msg!")?, - }, - spawn => BuildError::Spawn { - cmd: rf.cmd.ok_or("spawn error missing cmd!")?, - msg: rf.msg.ok_or("spawn failure without msg!")?, - }, - exit => BuildError::Exit { - cmd: rf.cmd.ok_or("exit error missing cmd!")?, - logs: rf - .logs - .ok_or("exit error missing logs!")? - .into_iter() - .map(|l| l.into()) - .collect(), - status: rf.status.map(|c| c as i32), - }, - output => BuildError::Output { - msg: rf.msg.ok_or("output failure without msg!")?, - }, - }) - } -} - -impl TryFrom<&NixFile> for rpc::ShellNix { - type Error = &'static str; - - fn try_from(nix_file: &NixFile) -> Result { - match nix_file.as_path().to_str() { - Some(s) => Ok(rpc::ShellNix { - path: s.to_string(), - }), - None => Err("nix file path is not UTF-8 clean"), - } - } -} - -impl std::convert::TryFrom for NixFile { - type Error = String; - - fn try_from(shell_nix: rpc::ShellNix) -> Result { - let path = PathBuf::from(shell_nix.path); - if path.as_path().is_file() { - Ok(NixFile(path)) - } else { - Err(format!("nix file {} does not exist", path.display())) - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 7ebde234..b5503ae0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,17 +29,30 @@ pub mod socket; pub mod thread; pub mod watch; -// This and the following module declaration together publicly export the contents of -// the generated module "com_target_lorri" as "rpc", which is a much nicer module name. -#[allow(missing_docs, clippy::all)] +// This and the following module declaration together publicly export the +// contents of the generated module "com_target_lorri" as "proto", which is a +// much nicer module name. +#[allow(missing_docs, clippy::all, unused_imports)] mod com_target_lorri; #[allow(missing_docs)] -pub mod rpc { +pub mod proto { // Code generated from com.target.lorri.varlink pub use super::com_target_lorri::*; } +// This and the following module declaration together publicly export the +// contents of the generated module "com_target_lorri_internal" as +// "internal_proto", which is a much nicer module name. +#[allow(missing_docs, clippy::all, unused_imports)] +mod com_target_lorri_internal; + +#[allow(missing_docs)] +pub(crate) mod internal_proto { + // Code generated from com.target.lorri.internal.varlink + pub use super::com_target_lorri_internal::*; +} + use std::path::{Path, PathBuf}; // OUT_DIR and build_rev.rs are generated by cargo, see ../build.rs @@ -68,6 +81,18 @@ impl From for NixFile { } } +impl ToString for NixFile { + fn to_string(&self) -> String { + self.as_path().to_string_lossy().to_string() + } +} + +impl From for NixFile { + fn from(s: String) -> Self { + NixFile(PathBuf::from(s)) + } +} + impl slog::Value for NixFile { fn serialize( &self, diff --git a/src/main.rs b/src/main.rs index d7e31d10..376995f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -120,7 +120,7 @@ fn run_command(log: slog::Logger, opts: Arguments) -> OpResult { Command::Internal { command } => match command { Internal_::Ping_(opts) => { let _guard = without_project(); - get_shell_nix(&opts.nix_file).and_then(ping::main) + get_shell_nix(&opts.nix_file).and_then(|nf| ping::main(nf, opts.socket_address)) } Internal_::StartUserShell_(opts) => { let (project, _guard) = with_project(&opts.nix_file)?; diff --git a/src/ops/direnv/mod.rs b/src/ops/direnv/mod.rs index 6b17d041..3d6b9194 100644 --- a/src/ops/direnv/mod.rs +++ b/src/ops/direnv/mod.rs @@ -3,10 +3,10 @@ mod version; use self::version::{DirenvVersion, MIN_DIRENV_VERSION}; +use crate::internal_proto; use crate::ops::error::{ok, ExitError, OpResult}; use crate::project::roots::Roots; use crate::project::Project; -use crate::rpc; use slog_scope::{info, warn}; use std::convert::TryFrom; use std::process::Command; @@ -19,24 +19,17 @@ pub fn main(project: Project, mut shell_output: W) -> OpResul let root_paths = Roots::from_project(&project).paths(); let paths_are_cached: bool = root_paths.all_exist(); let address = crate::ops::get_paths()?.daemon_socket_address(); - let shell_nix = rpc::ShellNix::try_from(&project.nix_file).map_err(ExitError::temporary)?; - - let ping_sent = match varlink::Connection::with_address(&address) { - Ok(connection) => { - use rpc::VarlinkClientInterface; - rpc::VarlinkClient::new(connection) - .watch_shell(shell_nix) - .call() - .expect("unable to ping varlink server"); - true - } - Err(err) => match err.kind() { - // We cannot connect to the socket - varlink::error::ErrorKind::Io(std::io::ErrorKind::NotFound) => false, - varlink::error::ErrorKind::Io(std::io::ErrorKind::ConnectionRefused) => false, - // Any other error is probably a bug - _ => panic!("failed connecting to socket: {:?}", err), - }, + let shell_nix = + internal_proto::ShellNix::try_from(&project.nix_file).map_err(ExitError::temporary)?; + + let ping_sent = if let Ok(connection) = varlink::Connection::with_address(&address) { + use internal_proto::VarlinkClientInterface; + internal_proto::VarlinkClient::new(connection) + .watch_shell(shell_nix) + .call() + .is_ok() + } else { + false }; match (ping_sent, paths_are_cached) { diff --git a/src/ops/mod.rs b/src/ops/mod.rs index 0a0c0404..c008a8c4 100644 --- a/src/ops/mod.rs +++ b/src/ops/mod.rs @@ -139,8 +139,8 @@ pub mod error { } } - impl From for ExitError { - fn from(e: crate::rpc::Error) -> ExitError { + impl From for ExitError { + fn from(e: crate::internal_proto::Error) -> ExitError { ExitError::temporary(format!("{}", e)) } } diff --git a/src/ops/ping.rs b/src/ops/ping.rs index dc50c71f..5e6f8c24 100644 --- a/src/ops/ping.rs +++ b/src/ops/ping.rs @@ -1,18 +1,21 @@ //! Run a BuildLoop for `shell.nix`, watching for input file changes. //! Can be used together with `direnv`. -use crate::ops::error::{ok, OpResult}; -use crate::rpc; +use crate::internal_proto; +use crate::ops::error::{ok, ExitError, OpResult}; use crate::NixFile; use std::convert::TryFrom; /// See the documentation for lorri::cli::Command::Ping_ for details. -pub fn main(nix_file: NixFile) -> OpResult { - // TODO: set up socket path, make it settable by the user - let address = crate::ops::get_paths()?.daemon_socket_address(); - let shell_nix = rpc::ShellNix::try_from(&nix_file).unwrap(); +pub fn main(nix_file: NixFile, addr: Option) -> OpResult { + let address = addr + .ok_or(Err(())) + .or_else::(|_: Result| { + Ok(crate::ops::get_paths()?.daemon_socket_address()) + })?; + let shell_nix = internal_proto::ShellNix::try_from(&nix_file).unwrap(); - use rpc::VarlinkClientInterface; - rpc::VarlinkClient::new( + use internal_proto::VarlinkClientInterface; + internal_proto::VarlinkClient::new( varlink::Connection::with_address(&address).expect("failed to connect to daemon server"), ) .watch_shell(shell_nix) diff --git a/src/ops/stream_events.rs b/src/ops/stream_events.rs index 52bb04dc..2c99f3eb 100644 --- a/src/ops/stream_events.rs +++ b/src/ops/stream_events.rs @@ -4,7 +4,7 @@ use crate::ops::{ error::{ok, ExitError, OpResult}, get_paths, }; -use crate::rpc; +use crate::proto; use crossbeam_channel::{select, unbounded}; use slog_scope::debug; use std::convert::TryInto; @@ -38,7 +38,7 @@ impl FromStr for EventKind { #[derive(Debug)] enum Error { - Varlink(rpc::Error), + Varlink(proto::Error), Compat(String), } @@ -47,8 +47,8 @@ enum Error { pub fn main(kind: EventKind) -> OpResult { let address = get_paths()?.daemon_socket_address(); - use rpc::VarlinkClientInterface; - let mut client = rpc::VarlinkClient::new( + use proto::VarlinkClientInterface; + let mut client = proto::VarlinkClient::new( varlink::Connection::with_address(&address).expect("failed to connect to daemon server"), ); diff --git a/src/socket.rs b/src/socket.rs index fd4331ed..7387c606 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -18,6 +18,12 @@ pub enum BindError { Unix(nix::Error), } +impl From for crate::ops::error::ExitError { + fn from(e: BindError) -> crate::ops::error::ExitError { + crate::ops::error::ExitError::temporary(format!("Bind error: {:?}", e)) + } +} + impl From for BindError { fn from(e: std::io::Error) -> BindError { BindError::Io(e) diff --git a/tests/daemon/main.rs b/tests/daemon/main.rs index 62600cbc..1ad40d46 100644 --- a/tests/daemon/main.rs +++ b/tests/daemon/main.rs @@ -2,7 +2,7 @@ use lorri::build_loop; use lorri::cas::ContentAddressable; use lorri::daemon::{Daemon, LoopHandlerEvent}; use lorri::nix::options::NixOptions; -use lorri::rpc; +use lorri::ops::ping; use lorri::socket::SocketPath; use std::io::{Error, ErrorKind}; use std::thread; @@ -36,14 +36,9 @@ pub fn start_job_with_ping() -> std::io::Result<()> { .expect("failed to serve daemon endpoint"); }); - // connect to socket and send a ping message - use lorri::rpc::VarlinkClientInterface; - rpc::VarlinkClient::new(connect(&address, Duration::from_millis(1000))) - .watch_shell(rpc::ShellNix { - path: shell_nix.to_str().unwrap().to_string(), - }) - .call() - .unwrap(); + connect(&address, Duration::from_millis(1000)); + + ping::main(lorri::NixFile::Shell(shell_nix), Some(address)).unwrap(); // Read the first build event, which should be a `Started` message match build_rx.recv_timeout(Duration::from_millis(1000)).unwrap() { From 340ac63e862955a099a7379634ec365f202e7c3a Mon Sep 17 00:00:00 2001 From: Judson Date: Mon, 18 May 2020 13:36:03 -0700 Subject: [PATCH 02/11] doc(release.nix): release note for public varlink interface --- release.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.nix b/release.nix index e9d0973e..74ec67d3 100644 --- a/release.nix +++ b/release.nix @@ -8,7 +8,7 @@ { version = 630; changes = '' - Make the event stream Varlink events public, with sum-style types. + Make the `lorri internal stream-events` Varlink events public, with sum-style types. ''; } { From 246d349c42756fd3120910ebf2352d38792a9d0d Mon Sep 17 00:00:00 2001 From: Judson Date: Thu, 21 May 2020 17:54:13 -0700 Subject: [PATCH 03/11] doc(varlink): Initial docs of the public varlink interface --- src/com.target.lorri.varlink | 54 +++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/src/com.target.lorri.varlink b/src/com.target.lorri.varlink index 91530b25..a2a85918 100644 --- a/src/com.target.lorri.varlink +++ b/src/com.target.lorri.varlink @@ -1,12 +1,22 @@ # The interface `lorri daemon` exposes. interface com.target.lorri -# Monitor the daemon. The method will reply with an update whenever a build begins or ends. -# Monitor will immediately reply with a snapshot of known projects, then a marker event, -# indicating that the stream of events is now "live." +# Monitor the daemon. The method will reply with an Event update whenever a +# build begins or ends. Monitor will immediately reply with a snapshot of +# known projects, then a marker event, indicating that the stream of events is +# now "live." method Monitor() -> (event: Event) +# An event describing the behavior of Lorri across all known projects. There +# are several kinds of Event, and each kind has a different type to represent +# futher information type Event ( + # The kind of the event: + # - section_end: marks the break between the current state snapshot, and + # live events. + # - started: a build has started but not completed + # - completed: a build completed successfully + # - failure: a build failed kind: (section_end, started, completed, failure), section: ?SectionMarker, # present iff kind == section_end reason: ?Reason, # present iff kind == started @@ -14,23 +24,47 @@ type Event ( failure: ?Failure # present iff kind == failure ) - +# An empty value - there is nothing further to distinguish the section end +# event. This type (and its field on Event) exist as a ward against future +# changes to the event, and to aid recipients in the meantime. type SectionMarker () +# The impetus for a new build. Like Event, Reason has a kind, and each kind has +# a unique field. type Reason ( + # The kind of build reason: + # - project_added: Lorri has been newly informed of a project + # - ping_received: A client requested a new build + # - files_changed: Lorri received a filesystem notification of changed files + # - unknown: A build started for an unknown reason kind: (project_added, ping_received, files_changed, unknown), + # The absolute path to the shell.nix file for the added project project: ?string, # present iff kind == project_added + # A list of files that changed, triggering a new build + # This can be useful e.g. to debug Nix expressions bringing in too many + # files and thereby building too frequently files: ?[]string, # present iff kind == files_changed + # A message describing the unknown cause for a new build. debug: ?string # present iff kind == unknown ) +# Details about the built project. type Outcome ( + # The absolute path to the shell.nix file for the added project nix_file: ?string, + # The root directory of the project project_root: string ) type Failure ( + # The kind of failure: + # - io: An I/O failure + # - spawn: The build process couldn't be spawned + # - exit: The build started but exited with a failure + # - output: the build completed, but Lorri wasn't able to interpret the + # output kind: (io, spawn, exit, output), + # The absolute path to the shell.nix file for the added project nix_file: string, io: ?IOFail, # present iff kind == io spawn: ?SpawnFail, # present iff kind == spawn @@ -38,21 +72,33 @@ type Failure ( output: ?OutputFail # present iff kind == output ) +# Describes a build failure related to opening files, usually the shell.nix file type IOFail ( + # A message describing the failure msg: string ) +# Describes a failure to launch the build process type SpawnFail ( + # A message describing the failure msg: string, + # The command Lorri attempted to execute cmd: string ) +# Describes a failed build process type ExitFail ( + # The command executed by Lorri cmd: string, + # The Unix exit status of the command, if available status: ?int, + # Log output of the command logs: []string ) +# Describes a failure caused by output produced by the build that Lorri cannot +# parse type OutputFail ( + # A message describing the failure msg: string ) From b4d7880aa36b6c0d4589bc88a5544bfbcf7ce57f Mon Sep 17 00:00:00 2001 From: Judson Date: Thu, 21 May 2020 19:36:47 -0700 Subject: [PATCH 04/11] feat(varlink): Updating interface based on docs --- src/com.target.lorri.varlink | 12 +++++------ src/com_target_lorri.rs | 14 ++++++------ src/daemon/internal_proto.rs | 42 +++++++++++++++++++++++------------- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/com.target.lorri.varlink b/src/com.target.lorri.varlink index a2a85918..233a27bc 100644 --- a/src/com.target.lorri.varlink +++ b/src/com.target.lorri.varlink @@ -51,7 +51,7 @@ type Reason ( # Details about the built project. type Outcome ( # The absolute path to the shell.nix file for the added project - nix_file: ?string, + nix_file: string, # The root directory of the project project_root: string ) @@ -75,21 +75,21 @@ type Failure ( # Describes a build failure related to opening files, usually the shell.nix file type IOFail ( # A message describing the failure - msg: string + message: string ) # Describes a failure to launch the build process type SpawnFail ( # A message describing the failure - msg: string, + message: string, # The command Lorri attempted to execute - cmd: string + command: string ) # Describes a failed build process type ExitFail ( # The command executed by Lorri - cmd: string, + command: string, # The Unix exit status of the command, if available status: ?int, # Log output of the command @@ -100,5 +100,5 @@ type ExitFail ( # parse type OutputFail ( # A message describing the failure - msg: string + message: string ) diff --git a/src/com_target_lorri.rs b/src/com_target_lorri.rs index 7e121037..5159bc24 100644 --- a/src/com_target_lorri.rs +++ b/src/com_target_lorri.rs @@ -125,7 +125,7 @@ pub struct r#Event { } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct r#ExitFail { - pub r#cmd: String, + pub r#command: String, pub r#status: Option, pub r#logs: Vec, } @@ -147,16 +147,16 @@ pub struct r#Failure { } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct r#IOFail { - pub r#msg: String, + pub r#message: String, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct r#Outcome { - pub r#nix_file: Option, + pub r#nix_file: String, pub r#project_root: String, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct r#OutputFail { - pub r#msg: String, + pub r#message: String, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub enum r#Reason_kind { @@ -176,8 +176,8 @@ pub struct r#Reason { pub struct r#SectionMarker {} #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct r#SpawnFail { - pub r#msg: String, - pub r#cmd: String, + pub r#message: String, + pub r#command: String, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct Monitor_Reply { @@ -234,7 +234,7 @@ pub fn new(inner: Box) -> VarlinkInterfacePr } impl varlink::Interface for VarlinkInterfaceProxy { fn get_description(&self) -> &'static str { - "# The interface `lorri daemon` exposes.\ninterface com.target.lorri\n\n# Monitor the daemon. The method will reply with an update whenever a build begins or ends.\n# Monitor will immediately reply with a snapshot of known projects, then a marker event,\n# indicating that the stream of events is now \"live.\"\nmethod Monitor() -> (event: Event)\n\ntype Event (\n kind: (section_end, started, completed, failure),\n section: ?SectionMarker, # present iff kind == section_end\n reason: ?Reason, # present iff kind == started\n result: ?Outcome, # present iff kind == completed\n failure: ?Failure # present iff kind == failure\n)\n\n\ntype SectionMarker ()\n\ntype Reason (\n kind: (project_added, ping_received, files_changed, unknown),\n project: ?string, # present iff kind == project_added\n files: ?[]string, # present iff kind == files_changed\n debug: ?string # present iff kind == unknown\n)\n\ntype Outcome (\n nix_file: ?string,\n project_root: string\n)\n\ntype Failure (\n kind: (io, spawn, exit, output),\n nix_file: string,\n io: ?IOFail, # present iff kind == io\n spawn: ?SpawnFail, # present iff kind == spawn\n exit: ?ExitFail, # present iff kind == exit\n output: ?OutputFail # present iff kind == output\n)\n\ntype IOFail (\n msg: string\n)\n\ntype SpawnFail (\n msg: string,\n cmd: string\n)\n\ntype ExitFail (\n cmd: string,\n status: ?int,\n logs: []string\n)\n\ntype OutputFail (\n msg: string\n)\n" + "# The interface `lorri daemon` exposes.\ninterface com.target.lorri\n\n# Monitor the daemon. The method will reply with an Event update whenever a\n# build begins or ends. Monitor will immediately reply with a snapshot of\n# known projects, then a marker event, indicating that the stream of events is\n# now \"live.\"\nmethod Monitor() -> (event: Event)\n\n# An event describing the behavior of Lorri across all known projects. There\n# are several kinds of Event, and each kind has a different type to represent\n# futher information\ntype Event (\n # The kind of the event:\n # - section_end: marks the break between the current state snapshot, and\n # live events.\n # - started: a build has started but not completed\n # - completed: a build completed successfully\n # - failure: a build failed\n kind: (section_end, started, completed, failure),\n section: ?SectionMarker, # present iff kind == section_end\n reason: ?Reason, # present iff kind == started\n result: ?Outcome, # present iff kind == completed\n failure: ?Failure # present iff kind == failure\n)\n\n# An empty value - there is nothing further to distinguish the section end\n# event. This type (and its field on Event) exist as a ward against future\n# changes to the event, and to aid recipients in the meantime.\ntype SectionMarker ()\n\n# The impetus for a new build. Like Event, Reason has a kind, and each kind has\n# a unique field.\ntype Reason (\n # The kind of build reason:\n # - project_added: Lorri has been newly informed of a project\n # - ping_received: A client requested a new build\n # - files_changed: Lorri received a filesystem notification of changed files\n # - unknown: A build started for an unknown reason\n kind: (project_added, ping_received, files_changed, unknown),\n # The absolute path to the shell.nix file for the added project\n project: ?string, # present iff kind == project_added\n # A list of files that changed, triggering a new build\n # This can be useful e.g. to debug Nix expressions bringing in too many\n # files and thereby building too frequently\n files: ?[]string, # present iff kind == files_changed\n # A message describing the unknown cause for a new build.\n debug: ?string # present iff kind == unknown\n)\n\n# Details about the built project.\ntype Outcome (\n # The absolute path to the shell.nix file for the added project\n nix_file: string,\n # The root directory of the project\n project_root: string\n)\n\ntype Failure (\n # The kind of failure:\n # - io: An I/O failure\n # - spawn: The build process couldn't be spawned\n # - exit: The build started but exited with a failure\n # - output: the build completed, but Lorri wasn't able to interpret the\n # output\n kind: (io, spawn, exit, output),\n # The absolute path to the shell.nix file for the added project\n nix_file: string,\n io: ?IOFail, # present iff kind == io\n spawn: ?SpawnFail, # present iff kind == spawn\n exit: ?ExitFail, # present iff kind == exit\n output: ?OutputFail # present iff kind == output\n)\n\n# Describes a build failure related to opening files, usually the shell.nix file\ntype IOFail (\n # A message describing the failure\n message: string\n)\n\n# Describes a failure to launch the build process\ntype SpawnFail (\n # A message describing the failure\n message: string,\n # The command Lorri attempted to execute\n command: string\n)\n\n# Describes a failed build process\ntype ExitFail (\n # The command executed by Lorri\n command: string,\n # The Unix exit status of the command, if available\n status: ?int,\n # Log output of the command\n logs: []string\n)\n\n# Describes a failure caused by output produced by the build that Lorri cannot\n# parse\ntype OutputFail (\n # A message describing the failure\n message: string\n)\n" } fn get_name(&self) -> &'static str { "com.target.lorri" diff --git a/src/daemon/internal_proto.rs b/src/daemon/internal_proto.rs index 90953cf2..2dbd5df0 100644 --- a/src/daemon/internal_proto.rs +++ b/src/daemon/internal_proto.rs @@ -259,7 +259,7 @@ impl TryFrom<&build_loop::Event> for proto::Outcome { fn try_from(ev: &build_loop::Event) -> Result { if let build_loop::Event::Completed { nix_file, result } = ev { Ok(proto::Outcome { - nix_file: Some(nix_file.to_string()), + nix_file: nix_file.to_string(), project_root: result.output_paths.shell_gc_root.to_string(), }) } else { @@ -273,7 +273,7 @@ impl TryFrom for build_loop::Event { fn try_from(o: proto::Outcome) -> Result { Ok(build_loop::Event::Completed { - nix_file: o.nix_file.clone().ok_or("missing nix file!")?.into(), + nix_file: o.nix_file.clone().into(), result: o.into(), }) } @@ -305,7 +305,9 @@ impl TryFrom<&build_loop::Event> for proto::Failure { BuildError::Io { msg } => proto::Failure { kind: io, nix_file: nix_file.to_string(), - io: Some(proto::IOFail { msg: msg.clone() }), + io: Some(proto::IOFail { + message: msg.clone(), + }), spawn: None, exit: None, output: None, @@ -315,8 +317,8 @@ impl TryFrom<&build_loop::Event> for proto::Failure { nix_file: nix_file.to_string(), io: None, spawn: Some(proto::SpawnFail { - cmd: cmd.clone(), - msg: msg.clone(), + command: cmd.clone(), + message: msg.clone(), }), exit: None, output: None, @@ -327,7 +329,7 @@ impl TryFrom<&build_loop::Event> for proto::Failure { io: None, spawn: None, exit: Some(proto::ExitFail { - cmd: cmd.clone(), + command: cmd.clone(), status: status.map(|i| i as i64), logs: logs.iter().map(|l| l.to_string()).collect(), }), @@ -339,7 +341,9 @@ impl TryFrom<&build_loop::Event> for proto::Failure { io: None, spawn: None, exit: None, - output: Some(proto::OutputFail { msg: msg.clone() }), + output: Some(proto::OutputFail { + message: msg.clone(), + }), }, }), _ => Err("expecting build_loop::Event::Failure"), @@ -368,28 +372,36 @@ impl TryFrom for error::BuildError { match pf { proto::Failure { kind: io, - io: Some(proto::IOFail { msg }), + io: Some(proto::IOFail { message }), .. - } => Ok(BuildError::Io { msg }), + } => Ok(BuildError::Io { msg: message }), proto::Failure { kind: spawn, - spawn: Some(proto::SpawnFail { cmd, msg }), + spawn: Some(proto::SpawnFail { command, message }), .. - } => Ok(BuildError::Spawn { cmd, msg }), + } => Ok(BuildError::Spawn { + cmd: command, + msg: message, + }), proto::Failure { kind: exit, - exit: Some(proto::ExitFail { cmd, status, logs }), + exit: + Some(proto::ExitFail { + command, + status, + logs, + }), .. } => Ok(BuildError::Exit { - cmd, + cmd: command, status: status.map(|i| i as i32), logs: logs.iter().map(|l| l.clone().into()).collect(), }), proto::Failure { kind: output, - output: Some(proto::OutputFail { msg }), + output: Some(proto::OutputFail { message }), .. - } => Ok(BuildError::Output { msg }), + } => Ok(BuildError::Output { msg: message }), _ => Err("unexpected form of proto::Failure"), } } From f5718cfcf5ae07c1405439dcb40c96b6a90af032 Mon Sep 17 00:00:00 2001 From: Judson Lester Date: Wed, 8 Jul 2020 12:35:37 -0700 Subject: [PATCH 05/11] chore(tests/daemon): Using absolute reference for ops in tests --- tests/daemon/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/daemon/main.rs b/tests/daemon/main.rs index 1ad40d46..82c914f0 100644 --- a/tests/daemon/main.rs +++ b/tests/daemon/main.rs @@ -2,7 +2,6 @@ use lorri::build_loop; use lorri::cas::ContentAddressable; use lorri::daemon::{Daemon, LoopHandlerEvent}; use lorri::nix::options::NixOptions; -use lorri::ops::ping; use lorri::socket::SocketPath; use std::io::{Error, ErrorKind}; use std::thread; @@ -38,7 +37,7 @@ pub fn start_job_with_ping() -> std::io::Result<()> { connect(&address, Duration::from_millis(1000)); - ping::main(lorri::NixFile::Shell(shell_nix), Some(address)).unwrap(); + lorri::ops::ping::main(lorri::NixFile::from(shell_nix), Some(address)).unwrap(); // Read the first build event, which should be a `Started` message match build_rx.recv_timeout(Duration::from_millis(1000)).unwrap() { From 94c549490b39669412c3e0e146cf19d8c44cd744 Mon Sep 17 00:00:00 2001 From: Judson Lester Date: Wed, 8 Jul 2020 13:40:29 -0700 Subject: [PATCH 06/11] chore(ops/ping): More readable optional handling --- src/ops/ping.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ops/ping.rs b/src/ops/ping.rs index 5e6f8c24..9f800a04 100644 --- a/src/ops/ping.rs +++ b/src/ops/ping.rs @@ -1,17 +1,16 @@ //! Run a BuildLoop for `shell.nix`, watching for input file changes. //! Can be used together with `direnv`. use crate::internal_proto; -use crate::ops::error::{ok, ExitError, OpResult}; +use crate::ops::error::{ok, OpResult}; use crate::NixFile; use std::convert::TryFrom; /// See the documentation for lorri::cli::Command::Ping_ for details. pub fn main(nix_file: NixFile, addr: Option) -> OpResult { - let address = addr - .ok_or(Err(())) - .or_else::(|_: Result| { - Ok(crate::ops::get_paths()?.daemon_socket_address()) - })?; + let address = match addr { + Some(a) => a, + None => crate::ops::get_paths()?.daemon_socket_address() + }; let shell_nix = internal_proto::ShellNix::try_from(&nix_file).unwrap(); use internal_proto::VarlinkClientInterface; From 8b70a59c534aaf86ad6decac1d09f5ec7e3452dd Mon Sep 17 00:00:00 2001 From: Philip Patsch Date: Wed, 29 Jul 2020 16:35:14 +0200 Subject: [PATCH 07/11] fix(varlink): fail if NixFile path is not valid utf-8 Varlink cannot encode arbitrary byte strings. We cannot serialize paths that are not valid utf8, so we have to fail. --- src/daemon/internal_proto.rs | 25 ++++++++++++++++--------- src/lib.rs | 6 ------ src/ops/ping.rs | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/daemon/internal_proto.rs b/src/daemon/internal_proto.rs index 2dbd5df0..36525a93 100644 --- a/src/daemon/internal_proto.rs +++ b/src/daemon/internal_proto.rs @@ -86,6 +86,13 @@ impl internal_proto::VarlinkInterface for Server { } } +fn try_nix_file_to_string(file: &NixFile) -> Result { + match file.as_path().to_str() { + None => Err(format!("file {} is not a valid utf-8 string. Varlink does not support non-utf8 strings, so we cannot serialize this file name. TODO: link issue", file.as_path().display())), + Some(s) => Ok(s.to_owned()) + } +} + impl proto::VarlinkInterface for Server { fn monitor(&self, call: &mut dyn proto::Call_Monitor) -> varlink::Result<()> { if !call.wants_more() { @@ -191,7 +198,7 @@ impl TryFrom for build_loop::Event { } impl TryFrom<&watch::Reason> for proto::Reason { - type Error = &'static str; + type Error = String; fn try_from(wr: &watch::Reason) -> Result { use proto::Reason_kind::*; @@ -206,7 +213,7 @@ impl TryFrom<&watch::Reason> for proto::Reason { }, Reason::ProjectAdded(nix_file) => proto::Reason { kind: project_added, - project: Some(nix_file.to_string()), + project: Some(try_nix_file_to_string(nix_file)?), files: None, debug: None, }, @@ -259,7 +266,7 @@ impl TryFrom<&build_loop::Event> for proto::Outcome { fn try_from(ev: &build_loop::Event) -> Result { if let build_loop::Event::Completed { nix_file, result } = ev { Ok(proto::Outcome { - nix_file: nix_file.to_string(), + nix_file: try_nix_file_to_string(nix_file)?, project_root: result.output_paths.shell_gc_root.to_string(), }) } else { @@ -294,7 +301,7 @@ impl From for build_loop::BuildResults { } impl TryFrom<&build_loop::Event> for proto::Failure { - type Error = &'static str; + type Error = String; fn try_from(ev: &build_loop::Event) -> Result { use error::BuildError; @@ -304,7 +311,7 @@ impl TryFrom<&build_loop::Event> for proto::Failure { build_loop::Event::Failure { nix_file, failure } => Ok(match failure { BuildError::Io { msg } => proto::Failure { kind: io, - nix_file: nix_file.to_string(), + nix_file: try_nix_file_to_string(nix_file)?, io: Some(proto::IOFail { message: msg.clone(), }), @@ -314,7 +321,7 @@ impl TryFrom<&build_loop::Event> for proto::Failure { }, BuildError::Spawn { cmd, msg } => proto::Failure { kind: spawn, - nix_file: nix_file.to_string(), + nix_file: try_nix_file_to_string(nix_file)?, io: None, spawn: Some(proto::SpawnFail { command: cmd.clone(), @@ -325,7 +332,7 @@ impl TryFrom<&build_loop::Event> for proto::Failure { }, BuildError::Exit { cmd, status, logs } => proto::Failure { kind: exit, - nix_file: nix_file.to_string(), + nix_file: try_nix_file_to_string(nix_file)?, io: None, spawn: None, exit: Some(proto::ExitFail { @@ -337,7 +344,7 @@ impl TryFrom<&build_loop::Event> for proto::Failure { }, BuildError::Output { msg } => proto::Failure { kind: output, - nix_file: nix_file.to_string(), + nix_file: try_nix_file_to_string(nix_file)?, io: None, spawn: None, exit: None, @@ -346,7 +353,7 @@ impl TryFrom<&build_loop::Event> for proto::Failure { }), }, }), - _ => Err("expecting build_loop::Event::Failure"), + _ => Err(String::from("expecting build_loop::Event::Failure")), } } } diff --git a/src/lib.rs b/src/lib.rs index b5503ae0..57828936 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,12 +81,6 @@ impl From for NixFile { } } -impl ToString for NixFile { - fn to_string(&self) -> String { - self.as_path().to_string_lossy().to_string() - } -} - impl From for NixFile { fn from(s: String) -> Self { NixFile(PathBuf::from(s)) diff --git a/src/ops/ping.rs b/src/ops/ping.rs index 9f800a04..d9cf4055 100644 --- a/src/ops/ping.rs +++ b/src/ops/ping.rs @@ -9,7 +9,7 @@ use std::convert::TryFrom; pub fn main(nix_file: NixFile, addr: Option) -> OpResult { let address = match addr { Some(a) => a, - None => crate::ops::get_paths()?.daemon_socket_address() + None => crate::ops::get_paths()?.daemon_socket_address(), }; let shell_nix = internal_proto::ShellNix::try_from(&nix_file).unwrap(); From 0cb7459591413567d2d8c7c4fdbbfd1c7dbd5c9f Mon Sep 17 00:00:00 2001 From: Philip Patsch Date: Wed, 29 Jul 2020 22:51:57 +0200 Subject: [PATCH 08/11] fix(treewide): remove serde::Deserialize instances for our types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Earlier we were using the Deserialize instances because our socket protocol was just the a trivial serde encoder/decoder. When we switched to varlink, the Serializers and Deserializers actually come from the varlink Value type, not from our data types, so we don’t need to implement Deserialize anymore. Now, we are still using Serialize, for the json streaming feature. We should probably make that more specific and separate out the event types for streaming on the command line. --- src/build_loop.rs | 4 ++-- src/builder.rs | 2 +- src/error.rs | 33 ++------------------------------- src/lib.rs | 2 +- src/watch.rs | 4 ++-- 5 files changed, 8 insertions(+), 37 deletions(-) diff --git a/src/build_loop.rs b/src/build_loop.rs index b9827aa9..cc935508 100644 --- a/src/build_loop.rs +++ b/src/build_loop.rs @@ -16,7 +16,7 @@ use slog_scope::{debug, warn}; use std::path::PathBuf; /// Builder events sent back over `BuildLoop.tx`. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum Event { /// Demarks a stream of events from recent history becoming live @@ -45,7 +45,7 @@ pub enum Event { } /// Results of a single, successful build. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize)] pub struct BuildResults { /// See `build::Info.outputPaths pub output_paths: builder::OutputPaths, diff --git a/src/builder.rs b/src/builder.rs index e31b0128..d5f270e6 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -287,7 +287,7 @@ where } /// Output paths generated by `logged-evaluation.nix` -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] pub struct OutputPaths { /// Shell path modified to work as a gc root pub shell_gc_root: T, diff --git a/src/error.rs b/src/error.rs index 9d224b56..d95638fa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,7 +6,7 @@ use std::io::Error as IoError; use std::process::{Command, ExitStatus}; /// An error that can occur during a build. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum BuildError { /// A system-level IO error occurred during the build. @@ -52,10 +52,7 @@ pub enum BuildError { }, } -use serde::{ - de::{self, Visitor}, - Deserialize, Deserializer, Serialize, Serializer, -}; +use serde::{Serialize, Serializer}; /// A line from stderr log output. Serializes more nicely than raw `OsString`. #[derive(Debug, Clone)] @@ -71,32 +68,6 @@ impl Serialize for LogLine { } } -impl<'de> Deserialize<'de> for LogLine { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct LLVisitor; - - impl<'de> Visitor<'de> for LLVisitor { - type Value = LogLine; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - Ok(LogLine(OsString::from(value))) - } - } - - deserializer.deserialize_str(LLVisitor) - } -} - impl From for LogLine { fn from(oss: OsString) -> Self { LogLine(oss) diff --git a/src/lib.rs b/src/lib.rs index 57828936..90bf397c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ use std::path::{Path, PathBuf}; include!(concat!(env!("OUT_DIR"), "/build_rev.rs")); /// A .nix file. -#[derive(Hash, PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +#[derive(Hash, PartialEq, Eq, Clone, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub struct NixFile(PathBuf); diff --git a/src/watch.rs b/src/watch.rs index 5c3677db..974dcee0 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -20,7 +20,7 @@ pub struct Watch { } /// A debug message string that can only be displayed via `Debug`. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize)] pub struct DebugMessage(String); impl From for DebugMessage { @@ -48,7 +48,7 @@ struct FilteredOut<'a> { } /// Description of the project change that triggered a build. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum Reason { /// When a project is presented to Lorri to track, it's built for this reason. From 0d582e0b460740e08bf419b80f179906a47e9ef2 Mon Sep 17 00:00:00 2001 From: Philip Patsch Date: Wed, 29 Jul 2020 23:11:04 +0200 Subject: [PATCH 09/11] fix(error.rs): make LogLine Display instance more explicit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We basically never want to display one LogLine on its own, always a list of log lines. So let’s only implement one very specific instance for a slice of LogLines, that also does the lossy utf8 encoding if necessary. --- src/com.target.lorri.varlink | 2 +- src/com_target_lorri.rs | 2 +- src/daemon/internal_proto.rs | 12 +++++++++++- src/error.rs | 32 +++++++++++++++++--------------- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/com.target.lorri.varlink b/src/com.target.lorri.varlink index 233a27bc..bf536285 100644 --- a/src/com.target.lorri.varlink +++ b/src/com.target.lorri.varlink @@ -92,7 +92,7 @@ type ExitFail ( command: string, # The Unix exit status of the command, if available status: ?int, - # Log output of the command + # stderr of the failed command. logs: []string ) diff --git a/src/com_target_lorri.rs b/src/com_target_lorri.rs index 5159bc24..21e7b5ca 100644 --- a/src/com_target_lorri.rs +++ b/src/com_target_lorri.rs @@ -234,7 +234,7 @@ pub fn new(inner: Box) -> VarlinkInterfacePr } impl varlink::Interface for VarlinkInterfaceProxy { fn get_description(&self) -> &'static str { - "# The interface `lorri daemon` exposes.\ninterface com.target.lorri\n\n# Monitor the daemon. The method will reply with an Event update whenever a\n# build begins or ends. Monitor will immediately reply with a snapshot of\n# known projects, then a marker event, indicating that the stream of events is\n# now \"live.\"\nmethod Monitor() -> (event: Event)\n\n# An event describing the behavior of Lorri across all known projects. There\n# are several kinds of Event, and each kind has a different type to represent\n# futher information\ntype Event (\n # The kind of the event:\n # - section_end: marks the break between the current state snapshot, and\n # live events.\n # - started: a build has started but not completed\n # - completed: a build completed successfully\n # - failure: a build failed\n kind: (section_end, started, completed, failure),\n section: ?SectionMarker, # present iff kind == section_end\n reason: ?Reason, # present iff kind == started\n result: ?Outcome, # present iff kind == completed\n failure: ?Failure # present iff kind == failure\n)\n\n# An empty value - there is nothing further to distinguish the section end\n# event. This type (and its field on Event) exist as a ward against future\n# changes to the event, and to aid recipients in the meantime.\ntype SectionMarker ()\n\n# The impetus for a new build. Like Event, Reason has a kind, and each kind has\n# a unique field.\ntype Reason (\n # The kind of build reason:\n # - project_added: Lorri has been newly informed of a project\n # - ping_received: A client requested a new build\n # - files_changed: Lorri received a filesystem notification of changed files\n # - unknown: A build started for an unknown reason\n kind: (project_added, ping_received, files_changed, unknown),\n # The absolute path to the shell.nix file for the added project\n project: ?string, # present iff kind == project_added\n # A list of files that changed, triggering a new build\n # This can be useful e.g. to debug Nix expressions bringing in too many\n # files and thereby building too frequently\n files: ?[]string, # present iff kind == files_changed\n # A message describing the unknown cause for a new build.\n debug: ?string # present iff kind == unknown\n)\n\n# Details about the built project.\ntype Outcome (\n # The absolute path to the shell.nix file for the added project\n nix_file: string,\n # The root directory of the project\n project_root: string\n)\n\ntype Failure (\n # The kind of failure:\n # - io: An I/O failure\n # - spawn: The build process couldn't be spawned\n # - exit: The build started but exited with a failure\n # - output: the build completed, but Lorri wasn't able to interpret the\n # output\n kind: (io, spawn, exit, output),\n # The absolute path to the shell.nix file for the added project\n nix_file: string,\n io: ?IOFail, # present iff kind == io\n spawn: ?SpawnFail, # present iff kind == spawn\n exit: ?ExitFail, # present iff kind == exit\n output: ?OutputFail # present iff kind == output\n)\n\n# Describes a build failure related to opening files, usually the shell.nix file\ntype IOFail (\n # A message describing the failure\n message: string\n)\n\n# Describes a failure to launch the build process\ntype SpawnFail (\n # A message describing the failure\n message: string,\n # The command Lorri attempted to execute\n command: string\n)\n\n# Describes a failed build process\ntype ExitFail (\n # The command executed by Lorri\n command: string,\n # The Unix exit status of the command, if available\n status: ?int,\n # Log output of the command\n logs: []string\n)\n\n# Describes a failure caused by output produced by the build that Lorri cannot\n# parse\ntype OutputFail (\n # A message describing the failure\n message: string\n)\n" + "# The interface `lorri daemon` exposes.\ninterface com.target.lorri\n\n# Monitor the daemon. The method will reply with an Event update whenever a\n# build begins or ends. Monitor will immediately reply with a snapshot of\n# known projects, then a marker event, indicating that the stream of events is\n# now \"live.\"\nmethod Monitor() -> (event: Event)\n\n# An event describing the behavior of Lorri across all known projects. There\n# are several kinds of Event, and each kind has a different type to represent\n# futher information\ntype Event (\n # The kind of the event:\n # - section_end: marks the break between the current state snapshot, and\n # live events.\n # - started: a build has started but not completed\n # - completed: a build completed successfully\n # - failure: a build failed\n kind: (section_end, started, completed, failure),\n section: ?SectionMarker, # present iff kind == section_end\n reason: ?Reason, # present iff kind == started\n result: ?Outcome, # present iff kind == completed\n failure: ?Failure # present iff kind == failure\n)\n\n# An empty value - there is nothing further to distinguish the section end\n# event. This type (and its field on Event) exist as a ward against future\n# changes to the event, and to aid recipients in the meantime.\ntype SectionMarker ()\n\n# The impetus for a new build. Like Event, Reason has a kind, and each kind has\n# a unique field.\ntype Reason (\n # The kind of build reason:\n # - project_added: Lorri has been newly informed of a project\n # - ping_received: A client requested a new build\n # - files_changed: Lorri received a filesystem notification of changed files\n # - unknown: A build started for an unknown reason\n kind: (project_added, ping_received, files_changed, unknown),\n # The absolute path to the shell.nix file for the added project\n project: ?string, # present iff kind == project_added\n # A list of files that changed, triggering a new build\n # This can be useful e.g. to debug Nix expressions bringing in too many\n # files and thereby building too frequently\n files: ?[]string, # present iff kind == files_changed\n # A message describing the unknown cause for a new build.\n debug: ?string # present iff kind == unknown\n)\n\n# Details about the built project.\ntype Outcome (\n # The absolute path to the shell.nix file for the added project\n nix_file: string,\n # The root directory of the project\n project_root: string\n)\n\ntype Failure (\n # The kind of failure:\n # - io: An I/O failure\n # - spawn: The build process couldn't be spawned\n # - exit: The build started but exited with a failure\n # - output: the build completed, but Lorri wasn't able to interpret the\n # output\n kind: (io, spawn, exit, output),\n # The absolute path to the shell.nix file for the added project\n nix_file: string,\n io: ?IOFail, # present iff kind == io\n spawn: ?SpawnFail, # present iff kind == spawn\n exit: ?ExitFail, # present iff kind == exit\n output: ?OutputFail # present iff kind == output\n)\n\n# Describes a build failure related to opening files, usually the shell.nix file\ntype IOFail (\n # A message describing the failure\n message: string\n)\n\n# Describes a failure to launch the build process\ntype SpawnFail (\n # A message describing the failure\n message: string,\n # The command Lorri attempted to execute\n command: string\n)\n\n# Describes a failed build process\ntype ExitFail (\n # The command executed by Lorri\n command: string,\n # The Unix exit status of the command, if available\n status: ?int,\n # stderr of the failed command.\n logs: []string\n)\n\n# Describes a failure caused by output produced by the build that Lorri cannot\n# parse\ntype OutputFail (\n # A message describing the failure\n message: string\n)\n" } fn get_name(&self) -> &'static str { "com.target.lorri" diff --git a/src/daemon/internal_proto.rs b/src/daemon/internal_proto.rs index 36525a93..b7da434e 100644 --- a/src/daemon/internal_proto.rs +++ b/src/daemon/internal_proto.rs @@ -86,6 +86,7 @@ impl internal_proto::VarlinkInterface for Server { } } +// TODO: remove when switchint to a protocol that can do [u8] fn try_nix_file_to_string(file: &NixFile) -> Result { match file.as_path().to_str() { None => Err(format!("file {} is not a valid utf-8 string. Varlink does not support non-utf8 strings, so we cannot serialize this file name. TODO: link issue", file.as_path().display())), @@ -93,6 +94,11 @@ fn try_nix_file_to_string(file: &NixFile) -> Result { } } +// TODO: remove when switchint to a protocol that can do [u8] +fn log_line_to_string(ll: &crate::error::LogLine) -> String { + ll.0.to_string_lossy().into_owned() +} + impl proto::VarlinkInterface for Server { fn monitor(&self, call: &mut dyn proto::Call_Monitor) -> varlink::Result<()> { if !call.wants_more() { @@ -107,6 +113,7 @@ impl proto::VarlinkInterface for Server { call.set_continues(true); for event in rx { debug!("event for varlink"; "event" => ?&event); + // TODO: destructure the owned event instead of a pointer to the event here match event.try_into() { Ok(ev) => call.reply(ev), Err(e) => call.reply_invalid_parameter(e.to_string()), @@ -116,6 +123,9 @@ impl proto::VarlinkInterface for Server { } } +// TODO: replace all these TryFrom instances with one explicit transformation function. +// This should reduce the boilerplate considerably. + impl TryFrom<&build_loop::Event> for proto::Event { type Error = String; @@ -338,7 +348,7 @@ impl TryFrom<&build_loop::Event> for proto::Failure { exit: Some(proto::ExitFail { command: cmd.clone(), status: status.map(|i| i as i64), - logs: logs.iter().map(|l| l.to_string()).collect(), + logs: logs.iter().map(log_line_to_string).collect(), }), output: None, }, diff --git a/src/error.rs b/src/error.rs index d95638fa..74386f2b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,6 +3,7 @@ use std::ffi::OsString; use std::fmt; use std::io::Error as IoError; +use std::os::unix::ffi::OsStrExt; use std::process::{Command, ExitStatus}; /// An error that can occur during a build. @@ -54,10 +55,13 @@ pub enum BuildError { use serde::{Serialize, Serializer}; -/// A line from stderr log output. Serializes more nicely than raw `OsString`. +/// A line from stderr log output. #[derive(Debug, Clone)] -pub struct LogLine(OsString); +pub struct LogLine(pub OsString); +/// Implement Serialize in a way that prints file names as strings. +/// TODO: this won’t return the actual filenames if they are not valid utf8. +/// so scripts won’t be able to read them. Maybe print a warning in that case? impl Serialize for LogLine { fn serialize(&self, serializer: S) -> Result where @@ -76,19 +80,20 @@ impl From for LogLine { impl From for LogLine { fn from(s: String) -> Self { - LogLine(s.into()) + LogLine(OsString::from(s)) } } -impl From for OsString { - fn from(ll: LogLine) -> Self { - ll.0 - } -} +struct LogLinesDisplay<'a>(&'a [LogLine]); -impl fmt::Display for LogLine { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", OsString::from(self.clone()).to_string_lossy()) +impl<'a> fmt::Display for LogLinesDisplay<'a> { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + for l in self.0 { + let mut s = String::from_utf8_lossy(l.0.as_bytes()).into_owned(); + s.push('\n'); + formatter.write_str(&s)?; + } + Ok(()) } } @@ -158,10 +163,7 @@ impl fmt::Display for BuildError { {}", status.map_or("".to_string(), |c| i32::to_string(&c)), cmd, - logs.iter() - .map(|l| l.to_string()) - .collect::>() - .join("\n") + LogLinesDisplay(logs) ), BuildError::Output { msg } => write!(f, "{}", msg), } From c967e2bd20820477a9b375e923127650f68f5f09 Mon Sep 17 00:00:00 2001 From: Philip Patsch Date: Wed, 29 Jul 2020 23:46:11 +0200 Subject: [PATCH 10/11] fix(varlink): show the same error as for NixFiles if file not utf8 --- src/daemon/internal_proto.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/daemon/internal_proto.rs b/src/daemon/internal_proto.rs index b7da434e..c7c275f9 100644 --- a/src/daemon/internal_proto.rs +++ b/src/daemon/internal_proto.rs @@ -14,6 +14,7 @@ use crate::NixFile; use crossbeam_channel as chan; use slog_scope::debug; use std::convert::{TryFrom, TryInto}; +use std::path::PathBuf; /// The daemon server. pub struct Server { @@ -72,7 +73,7 @@ impl internal_proto::VarlinkInterface for Server { call: &mut dyn internal_proto::Call_WatchShell, shell_nix: internal_proto::ShellNix, ) -> varlink::Result<()> { - let p = std::path::PathBuf::from(&shell_nix.path); + let p = PathBuf::from(&shell_nix.path); if p.is_file() { self.activity_tx .send(IndicateActivity { @@ -86,14 +87,19 @@ impl internal_proto::VarlinkInterface for Server { } } -// TODO: remove when switchint to a protocol that can do [u8] -fn try_nix_file_to_string(file: &NixFile) -> Result { - match file.as_path().to_str() { - None => Err(format!("file {} is not a valid utf-8 string. Varlink does not support non-utf8 strings, so we cannot serialize this file name. TODO: link issue", file.as_path().display())), +// TODO: remove when switching to a protocol that can do [u8] +fn try_file_to_string(file: &std::path::Path) -> Result { + match file.to_str() { + None => Err(format!("file {} is not a valid utf-8 string. Varlink does not support non-utf8 strings, so we cannot serialize this file name. TODO: link issue", file.display())), Some(s) => Ok(s.to_owned()) } } +// TODO: remove when switching to a protocol that can do [u8] +fn try_nix_file_to_string(file: &NixFile) -> Result { + try_file_to_string(file.as_path()) +} + // TODO: remove when switchint to a protocol that can do [u8] fn log_line_to_string(ll: &crate::error::LogLine) -> String { ll.0.to_string_lossy().into_owned() @@ -233,8 +239,8 @@ impl TryFrom<&watch::Reason> for proto::Reason { files: Some( changed .iter() - .map(|pb| Ok(pb.to_str().ok_or("cannot convert path!")?.to_string())) - .collect::, &'static str>>()?, + .map(|pb| try_file_to_string(&pb)) + .collect::, String>>()?, ), debug: None, }, From 348b5a48e1dbd85e942dfad920d91086088cbdf9 Mon Sep 17 00:00:00 2001 From: Philip Patsch Date: Wed, 29 Jul 2020 23:49:23 +0200 Subject: [PATCH 11/11] chore(varlink): replace uses of `into()` by explicit `::from` calls --- src/build_loop.rs | 2 +- src/daemon/internal_proto.rs | 27 ++++++++++++++++----------- src/watch.rs | 20 +------------------- 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/src/build_loop.rs b/src/build_loop.rs index cc935508..77bc40d3 100644 --- a/src/build_loop.rs +++ b/src/build_loop.rs @@ -91,7 +91,7 @@ impl<'a> BuildLoop<'a> { "message" => ?msg ); // can’t Clone `Event`s, so we return the Debug output here - Reason::UnknownEvent(DebugMessage::from(format!("{:#?}", msg))) + Reason::UnknownEvent(DebugMessage(format!("{:#?}", msg))) } Err(EventError::RxNoEventReceived) => { panic!("The file watcher died!"); diff --git a/src/daemon/internal_proto.rs b/src/daemon/internal_proto.rs index c7c275f9..f9a3264f 100644 --- a/src/daemon/internal_proto.rs +++ b/src/daemon/internal_proto.rs @@ -108,7 +108,7 @@ fn log_line_to_string(ll: &crate::error::LogLine) -> String { impl proto::VarlinkInterface for Server { fn monitor(&self, call: &mut dyn proto::Call_Monitor) -> varlink::Result<()> { if !call.wants_more() { - return call.reply_invalid_parameter("wants_more".into()); + return call.reply_invalid_parameter("wants_more".to_string()); } let (tx, rx) = chan::unbounded(); @@ -207,7 +207,7 @@ impl TryFrom for build_loop::Event { fn try_from(r: proto::Reason) -> Result { Ok(build_loop::Event::Started { - nix_file: r.project.clone().ok_or("missing nix file!")?.into(), + nix_file: NixFile::from(r.project.clone().ok_or("missing nix file!")?), reason: r.try_into()?, }) } @@ -248,7 +248,8 @@ impl TryFrom<&watch::Reason> for proto::Reason { kind: unknown, project: None, files: None, - debug: Some(dbg.into()), + // TODO + debug: Some(dbg.0.clone()), }, }) } @@ -263,15 +264,19 @@ impl TryFrom for watch::Reason { Ok(match rr.kind { ping_received => Reason::PingReceived, - project_added => Reason::ProjectAdded(rr.project.ok_or("missing nix file!")?.into()), + project_added => { + Reason::ProjectAdded(NixFile::from(rr.project.ok_or("missing nix file!")?)) + } files_changed => Reason::FilesChanged( rr.files .ok_or("missing files!")? .into_iter() - .map(|s| s.into()) + .map(PathBuf::from) .collect(), ), - unknown => Reason::UnknownEvent(rr.debug.ok_or("missing debug string!")?.into()), + unknown => Reason::UnknownEvent(watch::DebugMessage( + rr.debug.ok_or("missing debug string!")?, + )), }) } } @@ -296,8 +301,8 @@ impl TryFrom for build_loop::Event { fn try_from(o: proto::Outcome) -> Result { Ok(build_loop::Event::Completed { - nix_file: o.nix_file.clone().into(), - result: o.into(), + nix_file: NixFile::from(o.nix_file.clone()), + result: build_loop::BuildResults::from(o), }) } } @@ -310,7 +315,7 @@ impl From for build_loop::BuildResults { BuildResults { output_paths: OutputPaths { - shell_gc_root: RootPath(ro.project_root.into()), + shell_gc_root: RootPath(PathBuf::from(ro.project_root)), }, } } @@ -379,7 +384,7 @@ impl TryFrom for build_loop::Event { fn try_from(f: proto::Failure) -> Result { Ok(build_loop::Event::Failure { - nix_file: f.nix_file.clone().into(), + nix_file: NixFile::from(f.nix_file.clone()), failure: f.try_into()?, }) } @@ -418,7 +423,7 @@ impl TryFrom for error::BuildError { } => Ok(BuildError::Exit { cmd: command, status: status.map(|i| i as i32), - logs: logs.iter().map(|l| l.clone().into()).collect(), + logs: logs.into_iter().map(error::LogLine::from).collect(), }), proto::Failure { kind: output, diff --git a/src/watch.rs b/src/watch.rs index 974dcee0..1e15429e 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -21,25 +21,7 @@ pub struct Watch { /// A debug message string that can only be displayed via `Debug`. #[derive(Clone, Debug, Serialize)] -pub struct DebugMessage(String); - -impl From for DebugMessage { - fn from(s: String) -> Self { - DebugMessage(s) - } -} - -impl From for String { - fn from(d: DebugMessage) -> Self { - d.0 - } -} - -impl From<&DebugMessage> for String { - fn from(d: &DebugMessage) -> Self { - d.0.clone() - } -} +pub struct DebugMessage(pub String); #[derive(Debug, PartialEq, Eq)] struct FilteredOut<'a> {