diff --git a/Cargo.lock b/Cargo.lock index 7a0dc62..1fcddde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,6 +139,9 @@ name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-expr" @@ -497,6 +500,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "git2" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b989d6a7ca95a362cf2cfc5ad688b3a467be1f87e480b8dad07fee8c79b0044" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url", +] + [[package]] name = "glib" version = "0.17.10" @@ -843,6 +861,15 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -897,6 +924,34 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libgit2-sys" +version = "0.15.2+1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a80df2e11fb4a61f4ba2ab42dbe7f74468da143f1a75c74e11dee7c813f694fa" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libssh2-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + [[package]] name = "libusb" version = "0.3.0" @@ -918,6 +973,18 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libz-sys" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1471,6 +1538,7 @@ version = "0.1.0" dependencies = [ "anyhow", "gettext-rs", + "git2", "gtk4", "libusb", "nix", diff --git a/Cargo.toml b/Cargo.toml index 910200d..ad18ef1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ anyhow = "1.0.71" gettext-rs = { version = "0.7.0", features = [ "gettext-system" ] } +git2 = "0.17.2" gtk4 = { version = "0.6.6", features = [ "v4_10", ] } diff --git a/src/adb.rs b/src/adb.rs index ba6c2fb..70ea6fd 100644 --- a/src/adb.rs +++ b/src/adb.rs @@ -1,10 +1,10 @@ -use crate::runner::Runner; +use crate::cmd_runner::CmdRunner; use std::path::Path; -pub fn get_adb_install_runner(apk_path: &String) -> Runner { +pub fn get_adb_install_runner(apk_path: &String) -> CmdRunner { let path = Path::new(apk_path); path.try_exists().expect("APK file provided does not exist"); - Runner::new( + CmdRunner::new( None, "adb".into(), vec!["install".into(), path.to_str().unwrap().to_string()], diff --git a/src/build_tools/cmake.rs b/src/build_tools/cmake.rs index 6d69cd3..9f4dbc6 100644 --- a/src/build_tools/cmake.rs +++ b/src/build_tools/cmake.rs @@ -1,4 +1,4 @@ -use crate::runner::Runner; +use crate::cmd_runner::CmdRunner; use std::collections::HashMap; #[derive(Debug, Clone)] @@ -10,7 +10,7 @@ pub struct Cmake { } impl Cmake { - pub fn get_prepare_runner(&self) -> Runner { + pub fn get_prepare_runner(&self) -> CmdRunner { let mut args = vec![ "-B".into(), self.build_dir.clone(), @@ -30,19 +30,19 @@ impl Cmake { } } args.push(self.source_dir.clone()); - Runner::new(self.env.clone(), "cmake".into(), args) + CmdRunner::new(self.env.clone(), "cmake".into(), args) } - pub fn get_build_runner(&self) -> Runner { - Runner::new( + pub fn get_build_runner(&self) -> CmdRunner { + CmdRunner::new( self.env.clone(), "cmake".into(), vec!["--build".into(), self.build_dir.clone()], ) } - pub fn get_install_runner(&self) -> Runner { - Runner::new( + pub fn get_install_runner(&self) -> CmdRunner { + CmdRunner::new( self.env.clone(), "cmake".into(), vec!["--install".into(), self.build_dir.clone()], diff --git a/src/build_tools/git.rs b/src/build_tools/git.rs index cbc0844..82f72e8 100644 --- a/src/build_tools/git.rs +++ b/src/build_tools/git.rs @@ -1,7 +1,11 @@ +use crate::{ + cmd_runner::CmdRunner, + func_runner::{FuncRunner, FuncRunnerOut}, + profile::Profile, +}; +use git2::Repository; use std::path::Path; -use crate::{profile::Profile, runner::Runner}; - #[derive(Debug, Clone)] pub struct Git { pub repo: String, @@ -23,8 +27,8 @@ impl Git { split.next().map(|s| s.into()) } - pub fn get_reset_runner(&self) -> Runner { - Runner::new( + pub fn get_reset_runner(&self) -> CmdRunner { + CmdRunner::new( None, "git".into(), vec![ @@ -36,16 +40,52 @@ impl Git { ) } - pub fn get_pull_runner(&self) -> Runner { - Runner::new( + pub fn get_override_remote_url_runner(&self) -> FuncRunner { + let dir = self.dir.clone(); + let n_remote_url = self.get_repo(); + FuncRunner::new(Box::new(move || { + if let Ok(repo) = Repository::open(dir) { + if let Ok(remote) = repo.find_remote("origin") { + if remote.url().unwrap_or("") != n_remote_url { + if repo.remote_set_url("origin", &n_remote_url).is_ok() { + return FuncRunnerOut { + success: true, + out: vec![], + }; + } + return FuncRunnerOut { + success: false, + out: vec!["Failed to set origin remote url".into()], + }; + } + } else { + return FuncRunnerOut { + success: false, + out: vec!["Could not find remote origin".into()], + }; + } + return FuncRunnerOut { + success: true, + out: vec![], + }; + } + FuncRunnerOut { + success: true, + out: vec![], + } + })) + } + + pub fn get_pull_runner(&self) -> CmdRunner { + CmdRunner::new( None, "git".into(), vec!["-C".into(), self.dir.clone(), "pull".into()], ) } - pub fn get_clone_runner(&self) -> Runner { - Runner::new( + pub fn get_clone_runner(&self) -> CmdRunner { + CmdRunner::new( None, "git".into(), vec![ @@ -57,9 +97,9 @@ impl Git { ) } - pub fn get_checkout_ref_runner(&self) -> Option { + pub fn get_checkout_ref_runner(&self) -> Option { self.get_ref().map(|r| { - Runner::new( + CmdRunner::new( None, "git".into(), vec!["-C".into(), self.dir.clone(), "checkout".into(), r], @@ -67,7 +107,7 @@ impl Git { }) } - pub fn get_clone_or_not_runner(&self) -> Option { + pub fn get_clone_or_not_runner(&self) -> Option { let path_s = format!("{}/.git", self.dir.clone()); let path = Path::new(&path_s); if path.is_dir() { @@ -76,7 +116,7 @@ impl Git { Some(self.get_clone_runner()) } - pub fn clone_or_pull(&self, profile: &Profile) -> Option { + pub fn clone_or_pull(&self, profile: &Profile) -> Option { match self.get_clone_or_not_runner() { Some(r) => Some(r), None => match profile.pull_on_build { diff --git a/src/builders/build_basalt.rs b/src/builders/build_basalt.rs index 470947b..5de2be8 100644 --- a/src/builders/build_basalt.rs +++ b/src/builders/build_basalt.rs @@ -1,13 +1,14 @@ use crate::{ build_tools::{cmake::Cmake, git::Git}, + cmd_runner::CmdRunner, file_utils::rm_rf, profile::Profile, runner::Runner, }; use std::{collections::HashMap, path::Path}; -pub fn get_build_basalt_runners(profile: &Profile, clean_build: bool) -> Vec { - let mut runners: Vec = vec![]; +pub fn get_build_basalt_runners(profile: &Profile, clean_build: bool) -> Vec> { + let mut runners: Vec> = vec![]; let git = Git { repo: match profile.features.basalt.repo.as_ref() { Some(r) => r.clone(), @@ -15,13 +16,14 @@ pub fn get_build_basalt_runners(profile: &Profile, clean_build: bool) -> Vec Vec\\n/' include/pangolin/platform.h", repo = git.dir ), - ])); + ]))); if !Path::new(&build_dir).is_dir() || clean_build { rm_rf(&build_dir); - runners.push(cmake.get_prepare_runner()); + runners.push(Box::new(cmake.get_prepare_runner())); } - runners.push(cmake.get_build_runner()); - runners.push(cmake.get_install_runner()); + runners.push(Box::new(cmake.get_build_runner())); + runners.push(Box::new(cmake.get_install_runner())); - runners.push(Runner::new( + runners.push(Box::new(CmdRunner::new( None, "mkdir".into(), vec![ @@ -69,8 +71,8 @@ pub fn get_build_basalt_runners(profile: &Profile, clean_build: bool) -> Vec Vec Vec { - let mut runners: Vec = vec![]; +pub fn get_build_libsurvive_runners(profile: &Profile, clean_build: bool) -> Vec> { + let mut runners: Vec> = vec![]; let git = Git { repo: match profile.features.libsurvive.repo.as_ref() { Some(r) => r.clone(), @@ -15,13 +15,14 @@ pub fn get_build_libsurvive_runners(profile: &Profile, clean_build: bool) -> Vec }, dir: profile.features.libsurvive.path.as_ref().unwrap().clone(), }; + runners.push(Box::new(git.get_override_remote_url_runner())); if let Some(r) = git.clone_or_pull(profile) { - runners.push(r); + runners.push(Box::new(r)); }; if let Some(r) = git.get_checkout_ref_runner() { - runners.push(r); + runners.push(Box::new(r)); if profile.pull_on_build { - runners.push(git.get_pull_runner()); + runners.push(Box::new(git.get_pull_runner())); } } @@ -47,10 +48,10 @@ pub fn get_build_libsurvive_runners(profile: &Profile, clean_build: bool) -> Vec }; if !Path::new(&build_dir).is_dir() || clean_build { rm_rf(&build_dir); - runners.push(cmake.get_prepare_runner()); + runners.push(Box::new(cmake.get_prepare_runner())); } - runners.push(cmake.get_build_runner()); - runners.push(cmake.get_install_runner()); + runners.push(Box::new(cmake.get_build_runner())); + runners.push(Box::new(cmake.get_install_runner())); runners } diff --git a/src/builders/build_mercury.rs b/src/builders/build_mercury.rs index acb1320..66331b3 100644 --- a/src/builders/build_mercury.rs +++ b/src/builders/build_mercury.rs @@ -1,8 +1,10 @@ -use crate::{constants::pkg_data_dir, paths::get_cache_dir, profile::Profile, runner::Runner}; +use crate::{ + cmd_runner::CmdRunner, constants::pkg_data_dir, paths::get_cache_dir, profile::Profile, +}; -pub fn get_build_mercury_runner(profile: &Profile) -> Runner { +pub fn get_build_mercury_runner(profile: &Profile) -> CmdRunner { let args = vec![profile.prefix.clone(), get_cache_dir()]; - Runner::new( + CmdRunner::new( None, format!( "{sysdata}/scripts/build_mercury.sh", diff --git a/src/builders/build_monado.rs b/src/builders/build_monado.rs index cd9b60a..809cb00 100644 --- a/src/builders/build_monado.rs +++ b/src/builders/build_monado.rs @@ -6,8 +6,8 @@ use crate::{ }; use std::{collections::HashMap, path::Path}; -pub fn get_build_monado_runners(profile: &Profile, clean_build: bool) -> Vec { - let mut runners: Vec = vec![]; +pub fn get_build_monado_runners(profile: &Profile, clean_build: bool) -> Vec> { + let mut runners: Vec> = vec![]; let git = Git { repo: match profile.xrservice_repo.as_ref() { Some(r) => r.clone(), @@ -15,13 +15,14 @@ pub fn get_build_monado_runners(profile: &Profile, clean_build: bool) -> Vec Vec Vec { - let mut runners: Vec = vec![]; +pub fn get_build_opencomposite_runners( + profile: &Profile, + clean_build: bool, +) -> Vec> { + let mut runners: Vec> = vec![]; let git = Git { repo: match profile.opencomposite_repo.as_ref() { Some(r) => r.clone(), @@ -15,13 +18,14 @@ pub fn get_build_opencomposite_runners(profile: &Profile, clean_build: bool) -> }, dir: profile.opencomposite_path.clone(), }; + runners.push(Box::new(git.get_override_remote_url_runner())); if let Some(r) = git.clone_or_pull(profile) { - runners.push(r); + runners.push(Box::new(r)); }; if let Some(r) = git.get_checkout_ref_runner() { - runners.push(r); + runners.push(Box::new(r)); if profile.pull_on_build { - runners.push(git.get_pull_runner()); + runners.push(Box::new(git.get_pull_runner())); } } @@ -36,9 +40,9 @@ pub fn get_build_opencomposite_runners(profile: &Profile, clean_build: bool) -> }; if !Path::new(&build_dir).is_dir() || clean_build { rm_rf(&build_dir); - runners.push(cmake.get_prepare_runner()); + runners.push(Box::new(cmake.get_prepare_runner())); } - runners.push(cmake.get_build_runner()); + runners.push(Box::new(cmake.get_build_runner())); runners } diff --git a/src/builders/build_wivrn.rs b/src/builders/build_wivrn.rs index 5d25aa2..01e67fd 100644 --- a/src/builders/build_wivrn.rs +++ b/src/builders/build_wivrn.rs @@ -6,8 +6,8 @@ use crate::{ }; use std::{collections::HashMap, path::Path}; -pub fn get_build_wivrn_runners(profile: &Profile, clean_build: bool) -> Vec { - let mut runners: Vec = vec![]; +pub fn get_build_wivrn_runners(profile: &Profile, clean_build: bool) -> Vec> { + let mut runners: Vec> = vec![]; let git = Git { repo: match profile.xrservice_repo.as_ref() { Some(r) => r.clone(), @@ -15,13 +15,14 @@ pub fn get_build_wivrn_runners(profile: &Profile, clean_build: bool) -> Vec Vec, + 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 adb5260..1e83f8f 100644 --- a/src/file_utils.rs +++ b/src/file_utils.rs @@ -1,4 +1,4 @@ -use crate::runner::Runner; +use crate::{cmd_runner::CmdRunner, runner::Runner}; use nix::{ errno::Errno, sys::statvfs::{statvfs, FsFlags}, @@ -66,7 +66,7 @@ pub fn set_file_readonly(path_s: &String, readonly: bool) -> Result<(), std::io: } pub fn setcap_cap_sys_nice_eip(file: String) { - let mut runner = Runner::new( + let mut runner = CmdRunner::new( None, "pkexec".into(), vec!["setcap".into(), "CAP_SYS_NICE=eip".into(), file], diff --git a/src/func_runner.rs b/src/func_runner.rs new file mode 100644 index 0000000..1397e74 --- /dev/null +++ b/src/func_runner.rs @@ -0,0 +1,70 @@ +use std::{ + mem, + thread::{self, JoinHandle}, +}; + +use crate::runner::{Runner, RunnerStatus}; + +#[derive(Debug, Clone, Default)] +pub struct FuncRunnerOut { + pub success: bool, + pub out: Vec, +} + +pub struct FuncRunner { + output: Vec, + res: Option, + thread: Option>, + func: Box FuncRunnerOut + Send + Sync + 'static>, +} + +impl FuncRunner { + pub fn new(func: Box FuncRunnerOut + Send + Sync + 'static>) -> Self { + FuncRunner { + output: Vec::new(), + thread: None, + res: None, + func, + } + } +} + +impl Runner for FuncRunner { + fn start(&mut self) { + if self.res.is_some() { + panic!("Cannot start a FuncRunner twice!"); + } + let f = mem::replace(&mut self.func, Box::new(move || FuncRunnerOut::default())); + self.thread = Some(thread::spawn(move || f())); + } + + fn status(&mut self) -> RunnerStatus { + if let Some(res) = self.res { + if res { + return RunnerStatus::Stopped(Some(0)); + } + return RunnerStatus::Stopped(Some(1)); + } + if let Some(t) = self.thread.take() { + if t.is_finished() { + let out = t.join().expect("Failed to join thread"); + self.res = Some(out.success); + self.output = out.out; + return self.status(); + } + self.thread = Some(t); + return RunnerStatus::Running; + } + RunnerStatus::Stopped(None) + } + + fn consume_output(&mut self) -> String { + self.consume_rows().concat() + } + + fn consume_rows(&mut self) -> Vec { + let res = self.output.clone(); + self.output.clear(); + res + } +} diff --git a/src/main.rs b/src/main.rs index 840e970..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; @@ -25,6 +26,7 @@ pub mod downloader; pub mod env_var_descriptions; pub mod file_builders; pub mod file_utils; +pub mod func_runner; pub mod log_level; pub mod log_parser; pub mod paths; diff --git a/src/runner.rs b/src/runner.rs index 10fb64e..6daf84a 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -1,256 +1,12 @@ -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, -}; - -pub struct Runner { - 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 Runner { - 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(()) - } - - pub fn start(&mut self) { - self.try_start().expect("Failed to execute runner"); - } - - 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(); - } - - pub 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 receive_output(&mut self) { - while let Ok(data) = self.receiver.try_recv() { - self.output.push(data); - } - } - - pub fn consume_output(&mut self) -> String { - self.receive_output(); - let res = self.output.concat(); - self.output.clear(); - res - } - - pub fn consume_rows(&mut self) -> Vec { - self.receive_output(); - let res = self.output.clone(); - self.output.clear(); - res - } - - 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> { - Runner::save_log(path, &self.output) - } -} - -#[cfg(test)] -mod tests { - use crate::profile::Profile; - - use super::{Runner, RunnerStatus}; - 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 = Runner::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 = Runner::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() { - Runner::xrservice_runner_from_profile(&Profile::load_profile( - &"./test/files/profile.json".to_string(), - )); - } +pub trait Runner { + fn start(&mut self); + fn status(&mut self) -> RunnerStatus; + fn consume_output(&mut self) -> String; + fn consume_rows(&mut self) -> Vec; } diff --git a/src/runner_pipeline.rs b/src/runner_pipeline.rs index c2a0a44..09e874b 100644 --- a/src/runner_pipeline.rs +++ b/src/runner_pipeline.rs @@ -1,9 +1,8 @@ +use crate::runner::{Runner, RunnerStatus}; use std::cell::RefCell; -use crate::runner::{Runner, RunnerStatus}; - pub struct RunnerPipeline { - runners: Vec>, + runners: Vec>>, current_index: usize, last_exit_status: Option, has_started: bool, @@ -11,8 +10,8 @@ pub struct RunnerPipeline { } impl RunnerPipeline { - pub fn new(runners: Vec) -> Self { - let mut c_runners: Vec> = vec![]; + pub fn new(runners: Vec>) -> Self { + let mut c_runners: Vec>> = vec![]; for runner in runners { c_runners.push(RefCell::new(runner)); } @@ -25,7 +24,7 @@ impl RunnerPipeline { } } - pub fn get_current_runner(&self) -> Option<&RefCell> { + pub fn get_current_runner(&self) -> Option<&RefCell>> { self.runners.get(self.current_index) } diff --git a/src/ui/app.rs b/src/ui/app.rs index 007b917..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; @@ -76,7 +77,7 @@ pub struct App { #[tracker::do_not_track] config: Config, #[tracker::do_not_track] - xrservice_runner: Option, + xrservice_runner: Option, #[tracker::do_not_track] build_pipeline: Option, #[tracker::do_not_track] @@ -149,7 +150,7 @@ impl App { XRServiceType::Wivrn => true, // no device from log in wivrn }; self.debug_view.sender().emit(DebugViewMsg::ClearLog); - let mut runner = Runner::xrservice_runner_from_profile(&prof); + let mut runner = CmdRunner::xrservice_runner_from_profile(&prof); match runner.try_start() { Ok(_) => { self.xrservice_runner = Some(runner); @@ -400,7 +401,7 @@ impl SimpleComponent for App { Msg::BuildProfile(clean_build) => { let profile = self.get_selected_profile(); let mut missing_deps = vec![]; - let mut runners: Vec = vec![]; + let mut runners: Vec> = vec![]; // profile per se can't be built, but we still need opencomp if profile.can_be_built { missing_deps.extend(match profile.xrservice_type { @@ -417,7 +418,7 @@ impl SimpleComponent for App { } if profile.features.mercury_enabled { missing_deps.extend(get_missing_mercury_deps()); - runners.push(get_build_mercury_runner(&profile)); + runners.push(Box::new(get_build_mercury_runner(&profile))); } runners.extend(match profile.xrservice_type { XRServiceType::Monado => get_build_monado_runners(&profile, clean_build), diff --git a/src/ui/install_wivrn_box.rs b/src/ui/install_wivrn_box.rs index 3053513..9adacc7 100644 --- a/src/ui/install_wivrn_box.rs +++ b/src/ui/install_wivrn_box.rs @@ -1,5 +1,6 @@ use crate::{ adb::get_adb_install_runner, + cmd_runner::CmdRunner, depcheck::check_dependency, dependencies::adb_dep::adb_dep, downloader::download_file, @@ -31,7 +32,7 @@ pub struct InstallWivrnBox { #[tracker::do_not_track] download_thread: Option>>, #[tracker::do_not_track] - install_runner: Option, + install_runner: Option, #[tracker::do_not_track] root_win: gtk::Window, } diff --git a/src/ui/libsurvive_setup_window.rs b/src/ui/libsurvive_setup_window.rs index ab929d1..f00c471 100644 --- a/src/ui/libsurvive_setup_window.rs +++ b/src/ui/libsurvive_setup_window.rs @@ -1,4 +1,4 @@ -use crate::{profile::Profile, runner::Runner}; +use crate::{cmd_runner::CmdRunner, profile::Profile, runner::Runner}; use adw::prelude::*; use gtk::glib; use relm4::prelude::*; @@ -40,7 +40,7 @@ pub struct LibsurviveSetupWindow { #[tracker::do_not_track] profile: Option, #[tracker::do_not_track] - calibration_runner: Option, + calibration_runner: Option, } #[derive(Debug)] @@ -54,14 +54,14 @@ pub enum LibsurviveSetupMsg { } impl LibsurviveSetupWindow { - fn create_calibration_runner(&mut self, survive_cli_path: String) -> Runner { + fn create_calibration_runner(&mut self, survive_cli_path: String) -> CmdRunner { let lh_path = self.steam_lighthouse_path.clone(); let mut env = HashMap::new(); env.insert( "LD_LIBRARY_PATH".to_string(), format!("{pfx}/lib", pfx = self.profile.as_ref().unwrap().prefix), ); - Runner::new( + CmdRunner::new( Some(env), survive_cli_path, vec!["--steamvr-calibration".into(), lh_path],