diff --git a/src/adb.rs b/src/adb.rs index 6be82df..70ea6fd 100644 --- a/src/adb.rs +++ b/src/adb.rs @@ -1,4 +1,4 @@ -use crate::runner::CmdRunner; +use crate::cmd_runner::CmdRunner; use std::path::Path; pub fn get_adb_install_runner(apk_path: &String) -> CmdRunner { diff --git a/src/build_tools/cmake.rs b/src/build_tools/cmake.rs index 436b99e..9f4dbc6 100644 --- a/src/build_tools/cmake.rs +++ b/src/build_tools/cmake.rs @@ -1,4 +1,4 @@ -use crate::runner::CmdRunner; +use crate::cmd_runner::CmdRunner; use std::collections::HashMap; #[derive(Debug, Clone)] diff --git a/src/build_tools/git.rs b/src/build_tools/git.rs index 2d6da13..82f72e8 100644 --- a/src/build_tools/git.rs +++ b/src/build_tools/git.rs @@ -1,7 +1,7 @@ use crate::{ + cmd_runner::CmdRunner, func_runner::{FuncRunner, FuncRunnerOut}, profile::Profile, - runner::CmdRunner, }; use git2::Repository; use std::path::Path; diff --git a/src/builders/build_basalt.rs b/src/builders/build_basalt.rs index a16cbb2..5de2be8 100644 --- a/src/builders/build_basalt.rs +++ b/src/builders/build_basalt.rs @@ -1,8 +1,9 @@ use crate::{ build_tools::{cmake::Cmake, git::Git}, + cmd_runner::CmdRunner, file_utils::rm_rf, profile::Profile, - runner::{CmdRunner, Runner}, + runner::Runner, }; use std::{collections::HashMap, path::Path}; diff --git a/src/builders/build_mercury.rs b/src/builders/build_mercury.rs index bd9df1d..66331b3 100644 --- a/src/builders/build_mercury.rs +++ b/src/builders/build_mercury.rs @@ -1,4 +1,6 @@ -use crate::{constants::pkg_data_dir, paths::get_cache_dir, profile::Profile, runner::CmdRunner}; +use crate::{ + cmd_runner::CmdRunner, constants::pkg_data_dir, paths::get_cache_dir, profile::Profile, +}; pub fn get_build_mercury_runner(profile: &Profile) -> CmdRunner { let args = vec![profile.prefix.clone(), get_cache_dir()]; diff --git a/src/cmd_runner.rs b/src/cmd_runner.rs new file mode 100644 index 0000000..15eb37f --- /dev/null +++ b/src/cmd_runner.rs @@ -0,0 +1,253 @@ +use nix::{ + sys::signal::{kill, Signal::SIGTERM}, + unistd::Pid, +}; + +use crate::{ + file_utils::get_writer, + profile::{Profile, XRServiceType}, + runner::{Runner, RunnerStatus}, +}; +use std::{ + collections::HashMap, + io::{BufRead, BufReader, Write}, + process::{Child, Command, Stdio}, + sync::{ + mpsc::{sync_channel, Receiver, SyncSender}, + Arc, Mutex, + }, + thread::{self, JoinHandle}, +}; + +pub struct CmdRunner { + pub environment: HashMap, + pub command: String, + pub args: Vec, + pub output: Vec, + sender: Arc>>, + receiver: Receiver, + threads: Vec>, + process: Option, +} + +macro_rules! logger_thread { + ($buf_fd: expr, $sender: expr) => { + thread::spawn(move || { + let mut reader = BufReader::new($buf_fd); + loop { + let mut buf = String::new(); + match reader.read_line(&mut buf) { + Err(_) => return, + Ok(bytes_read) => { + if bytes_read == 0 { + return; + } + if buf.is_empty() { + continue; + } + print!("{}", buf); + match $sender + .clone() + .lock() + .expect("Could not lock sender") + .send(buf) + { + Ok(_) => {} + Err(_) => return, + }; + } + }; + } + }) + }; +} + +impl CmdRunner { + pub fn new( + environment: Option>, + command: String, + args: Vec, + ) -> Self { + let (sender, receiver) = sync_channel(64000); + Self { + environment: match environment { + None => HashMap::new(), + Some(e) => e.clone(), + }, + command, + args, + output: Vec::new(), + process: None, + sender: Arc::new(Mutex::new(sender)), + threads: Vec::new(), + receiver, + } + } + + pub fn xrservice_runner_from_profile(profile: &Profile) -> Self { + let mut env = profile.environment.clone(); + if !env.contains_key("LH_DRIVER") { + env.insert( + "LH_DRIVER".into(), + profile.lighthouse_driver.to_string().to_lowercase(), + ); + } + Self::new( + Some(env), + match profile.xrservice_type { + XRServiceType::Monado => format!("{pfx}/bin/monado-service", pfx = profile.prefix), + XRServiceType::Wivrn => format!("{pfx}/bin/wivrn-server", pfx = profile.prefix), + }, + vec![], + ) + } + + pub fn try_start(&mut self) -> Result<(), std::io::Error> { + self.threads = Vec::new(); + let cmd = Command::new(self.command.clone()) + .args(self.args.clone()) + .envs(self.environment.clone()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; + self.process = Some(cmd); + let stdout = self.process.as_mut().unwrap().stdout.take().unwrap(); + let stderr = self.process.as_mut().unwrap().stderr.take().unwrap(); + let stdout_sender = self.sender.clone(); + let stderr_sender = self.sender.clone(); + self.threads.push(logger_thread!(stdout, stdout_sender)); + self.threads.push(logger_thread!(stderr, stderr_sender)); + Ok(()) + } + + fn join_threads(&mut self) { + loop { + match self.threads.pop() { + None => break, + Some(thread) => thread.join().expect("Failed to join reader thread"), + } + } + } + + pub fn terminate(&mut self) { + if self.status() != RunnerStatus::Running { + return; + } + let process = self.process.take(); + if process.is_none() { + return; + } + let mut proc = process.unwrap(); + let child_pid = Pid::from_raw(proc.id().try_into().expect("Could not convert pid to u32")); + kill(child_pid, SIGTERM).expect("Could not send sigterm to process"); + self.join_threads(); + proc.wait().expect("Failed to wait for process"); + } + + pub fn join(&mut self) { + let process = self.process.take(); + if process.is_none() { + return; + } + let mut proc = process.unwrap(); + proc.wait().expect("Failed to wait for process"); + self.join_threads(); + } + + fn receive_output(&mut self) { + while let Ok(data) = self.receiver.try_recv() { + self.output.push(data); + } + } + + fn save_log(path_s: String, log: &[String]) -> Result<(), std::io::Error> { + let mut writer = get_writer(&path_s); + let log_s = log.concat(); + writer.write_all(log_s.as_ref()) + } + + pub fn save_output(&mut self, path: String) -> Result<(), std::io::Error> { + CmdRunner::save_log(path, &self.output) + } +} + +impl Runner for CmdRunner { + fn consume_output(&mut self) -> String { + self.receive_output(); + let res = self.output.concat(); + self.output.clear(); + res + } + + fn consume_rows(&mut self) -> Vec { + self.receive_output(); + let res = self.output.clone(); + self.output.clear(); + res + } + + fn status(&mut self) -> RunnerStatus { + match &mut self.process { + None => RunnerStatus::Stopped(None), + Some(proc) => match proc.try_wait() { + Err(_) => RunnerStatus::Running, + Ok(Some(code)) => RunnerStatus::Stopped(code.code()), + Ok(None) => RunnerStatus::Running, + }, + } + } + + fn start(&mut self) { + self.try_start().expect("Failed to execute runner"); + } +} + +#[cfg(test)] +mod tests { + use super::{CmdRunner, RunnerStatus}; + use crate::profile::Profile; + use crate::runner::Runner; + use core::time; + use std::{collections::HashMap, thread::sleep}; + + #[test] + fn can_run_command_and_read_env() { + let mut env = HashMap::new(); + env.insert("REX2TEST".to_string(), "Lorem ipsum dolor".to_string()); + let mut runner = CmdRunner::new( + Some(env), + "bash".into(), + vec!["-c".into(), "echo \"REX2TEST: $REX2TEST\"".into()], + ); + runner.start(); + sleep(time::Duration::from_millis(1000)); // TODO: ugly, fix + runner.terminate(); + assert_eq!(runner.status(), RunnerStatus::Stopped(Some(0))); + let out = runner.consume_output(); + assert_eq!(out, "REX2TEST: Lorem ipsum dolor\n"); + } + + #[test] + fn can_save_log() { + let mut runner = CmdRunner::new( + None, + "bash".into(), + vec!["-c".into(), "echo \"Lorem ipsum dolor sit amet\"".into()], + ); + runner.start(); + while runner.status() == RunnerStatus::Running { + sleep(time::Duration::from_millis(10)); + } + + runner + .save_output("./target/testout/testlog".into()) + .expect("Failed to save output file"); + } + + #[test] + fn can_create_from_profile() { + CmdRunner::xrservice_runner_from_profile(&Profile::load_profile( + &"./test/files/profile.json".to_string(), + )); + } +} diff --git a/src/file_utils.rs b/src/file_utils.rs index e91f3ec..1e83f8f 100644 --- a/src/file_utils.rs +++ b/src/file_utils.rs @@ -1,4 +1,4 @@ -use crate::runner::{CmdRunner, Runner}; +use crate::{cmd_runner::CmdRunner, runner::Runner}; use nix::{ errno::Errno, sys::statvfs::{statvfs, FsFlags}, diff --git a/src/main.rs b/src/main.rs index 6774891..6603097 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ pub mod adb; pub mod build_tools; pub mod builders; pub mod checkerr; +pub mod cmd_runner; pub mod config; pub mod constants; pub mod depcheck; diff --git a/src/runner.rs b/src/runner.rs index 5bdc2e6..6daf84a 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -1,22 +1,8 @@ -use crate::{ - file_utils::get_writer, - profile::{Profile, XRServiceType}, -}; -use nix::{ - sys::signal::{kill, Signal::SIGTERM}, - unistd::Pid, -}; -use std::{ - collections::HashMap, - io::{BufRead, BufReader, Write}, - process::{Child, Command, Stdio}, - sync::{ - mpsc::{sync_channel, Receiver, SyncSender}, - Arc, Mutex, - }, - thread::{self, JoinHandle}, - vec, -}; +#[derive(PartialEq, Eq, Debug)] +pub enum RunnerStatus { + Running, + Stopped(Option), +} pub trait Runner { fn start(&mut self); @@ -24,242 +10,3 @@ pub trait Runner { fn consume_output(&mut self) -> String; fn consume_rows(&mut self) -> Vec; } - -pub struct CmdRunner { - pub environment: HashMap, - pub command: String, - pub args: Vec, - pub output: Vec, - sender: Arc>>, - receiver: Receiver, - threads: Vec>, - process: Option, -} - -#[derive(PartialEq, Eq, Debug)] -pub enum RunnerStatus { - Running, - Stopped(Option), -} - -macro_rules! logger_thread { - ($buf_fd: expr, $sender: expr) => { - thread::spawn(move || { - let mut reader = BufReader::new($buf_fd); - loop { - let mut buf = String::new(); - match reader.read_line(&mut buf) { - Err(_) => return, - Ok(bytes_read) => { - if bytes_read == 0 { - return; - } - if buf.is_empty() { - continue; - } - print!("{}", buf); - match $sender - .clone() - .lock() - .expect("Could not lock sender") - .send(buf) - { - Ok(_) => {} - Err(_) => return, - }; - } - }; - } - }) - }; -} - -impl CmdRunner { - pub fn new( - environment: Option>, - command: String, - args: Vec, - ) -> Self { - let (sender, receiver) = sync_channel(64000); - Self { - environment: match environment { - None => HashMap::new(), - Some(e) => e.clone(), - }, - command, - args, - output: Vec::new(), - process: None, - sender: Arc::new(Mutex::new(sender)), - threads: Vec::new(), - receiver, - } - } - - pub fn xrservice_runner_from_profile(profile: &Profile) -> Self { - let mut env = profile.environment.clone(); - if !env.contains_key("LH_DRIVER") { - env.insert( - "LH_DRIVER".into(), - profile.lighthouse_driver.to_string().to_lowercase(), - ); - } - Self::new( - Some(env), - match profile.xrservice_type { - XRServiceType::Monado => format!("{pfx}/bin/monado-service", pfx = profile.prefix), - XRServiceType::Wivrn => format!("{pfx}/bin/wivrn-server", pfx = profile.prefix), - }, - vec![], - ) - } - - pub fn try_start(&mut self) -> Result<(), std::io::Error> { - self.threads = Vec::new(); - let cmd = Command::new(self.command.clone()) - .args(self.args.clone()) - .envs(self.environment.clone()) - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; - self.process = Some(cmd); - let stdout = self.process.as_mut().unwrap().stdout.take().unwrap(); - let stderr = self.process.as_mut().unwrap().stderr.take().unwrap(); - let stdout_sender = self.sender.clone(); - let stderr_sender = self.sender.clone(); - self.threads.push(logger_thread!(stdout, stdout_sender)); - self.threads.push(logger_thread!(stderr, stderr_sender)); - Ok(()) - } - - fn join_threads(&mut self) { - loop { - match self.threads.pop() { - None => break, - Some(thread) => thread.join().expect("Failed to join reader thread"), - } - } - } - - pub fn terminate(&mut self) { - if self.status() != RunnerStatus::Running { - return; - } - let process = self.process.take(); - if process.is_none() { - return; - } - let mut proc = process.unwrap(); - let child_pid = Pid::from_raw(proc.id().try_into().expect("Could not convert pid to u32")); - kill(child_pid, SIGTERM).expect("Could not send sigterm to process"); - self.join_threads(); - proc.wait().expect("Failed to wait for process"); - } - - pub fn join(&mut self) { - let process = self.process.take(); - if process.is_none() { - return; - } - let mut proc = process.unwrap(); - proc.wait().expect("Failed to wait for process"); - self.join_threads(); - } - - fn receive_output(&mut self) { - while let Ok(data) = self.receiver.try_recv() { - self.output.push(data); - } - } - - fn save_log(path_s: String, log: &[String]) -> Result<(), std::io::Error> { - let mut writer = get_writer(&path_s); - let log_s = log.concat(); - writer.write_all(log_s.as_ref()) - } - - pub fn save_output(&mut self, path: String) -> Result<(), std::io::Error> { - CmdRunner::save_log(path, &self.output) - } -} - -impl Runner for CmdRunner { - fn consume_output(&mut self) -> String { - self.receive_output(); - let res = self.output.concat(); - self.output.clear(); - res - } - - fn consume_rows(&mut self) -> Vec { - self.receive_output(); - let res = self.output.clone(); - self.output.clear(); - res - } - - fn status(&mut self) -> RunnerStatus { - match &mut self.process { - None => RunnerStatus::Stopped(None), - Some(proc) => match proc.try_wait() { - Err(_) => RunnerStatus::Running, - Ok(Some(code)) => RunnerStatus::Stopped(code.code()), - Ok(None) => RunnerStatus::Running, - }, - } - } - - fn start(&mut self) { - self.try_start().expect("Failed to execute runner"); - } -} - -#[cfg(test)] -mod tests { - use super::{CmdRunner, RunnerStatus}; - use crate::profile::Profile; - use crate::runner::Runner; - use core::time; - use std::{collections::HashMap, thread::sleep}; - - #[test] - fn can_run_command_and_read_env() { - let mut env = HashMap::new(); - env.insert("REX2TEST".to_string(), "Lorem ipsum dolor".to_string()); - let mut runner = CmdRunner::new( - Some(env), - "bash".into(), - vec!["-c".into(), "echo \"REX2TEST: $REX2TEST\"".into()], - ); - runner.start(); - sleep(time::Duration::from_millis(1000)); // TODO: ugly, fix - runner.terminate(); - assert_eq!(runner.status(), RunnerStatus::Stopped(Some(0))); - let out = runner.consume_output(); - assert_eq!(out, "REX2TEST: Lorem ipsum dolor\n"); - } - - #[test] - fn can_save_log() { - let mut runner = CmdRunner::new( - None, - "bash".into(), - vec!["-c".into(), "echo \"Lorem ipsum dolor sit amet\"".into()], - ); - runner.start(); - while runner.status() == RunnerStatus::Running { - sleep(time::Duration::from_millis(10)); - } - - runner - .save_output("./target/testout/testlog".into()) - .expect("Failed to save output file"); - } - - #[test] - fn can_create_from_profile() { - CmdRunner::xrservice_runner_from_profile(&Profile::load_profile( - &"./test/files/profile.json".to_string(), - )); - } -} diff --git a/src/ui/app.rs b/src/ui/app.rs index 5ba8a11..0cf8619 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -10,6 +10,7 @@ use crate::builders::build_mercury::get_build_mercury_runner; use crate::builders::build_monado::get_build_monado_runners; use crate::builders::build_opencomposite::get_build_opencomposite_runners; use crate::builders::build_wivrn::get_build_wivrn_runners; +use crate::cmd_runner::CmdRunner; use crate::config::Config; use crate::constants::APP_NAME; use crate::depcheck::check_dependency; @@ -32,7 +33,7 @@ use crate::profiles::lighthouse::lighthouse_profile; use crate::profiles::system_valve_index::system_valve_index_profile; use crate::profiles::valve_index::valve_index_profile; use crate::profiles::wivrn::wivrn_profile; -use crate::runner::{CmdRunner, Runner, RunnerStatus}; +use crate::runner::{Runner, RunnerStatus}; use crate::runner_pipeline::RunnerPipeline; use crate::ui::build_window::BuildWindowMsg; use crate::ui::debug_view::DebugViewInit; diff --git a/src/ui/install_wivrn_box.rs b/src/ui/install_wivrn_box.rs index 5b74650..9adacc7 100644 --- a/src/ui/install_wivrn_box.rs +++ b/src/ui/install_wivrn_box.rs @@ -1,11 +1,12 @@ use crate::{ adb::get_adb_install_runner, + cmd_runner::CmdRunner, depcheck::check_dependency, dependencies::adb_dep::adb_dep, downloader::download_file, paths::wivrn_apk_download_path, profile::{Profile, XRServiceType}, - runner::{CmdRunner, Runner, RunnerStatus}, + runner::{Runner, RunnerStatus}, }; use gtk::prelude::*; use relm4::{ diff --git a/src/ui/libsurvive_setup_window.rs b/src/ui/libsurvive_setup_window.rs index 4b7565d..f00c471 100644 --- a/src/ui/libsurvive_setup_window.rs +++ b/src/ui/libsurvive_setup_window.rs @@ -1,7 +1,4 @@ -use crate::{ - profile::Profile, - runner::{CmdRunner, Runner}, -}; +use crate::{cmd_runner::CmdRunner, profile::Profile, runner::Runner}; use adw::prelude::*; use gtk::glib; use relm4::prelude::*;