diff --git a/path.cfg b/path.cfg index dd711ee..2e2a049 100644 --- a/path.cfg +++ b/path.cfg @@ -1,7 +1,8 @@ mmseqs=mmseqs foldseek=foldseek +foldmason=foldmason mafft=mafft mafft-linsi=mafft-linsi -foldmason=foldmason iqtree=iqtree -#fasttree=fasttree \ No newline at end of file +#fasttree= +#raxml= \ No newline at end of file diff --git a/src/envs/error_handler.rs b/src/envs/error_handler.rs index 2d383ec..e677e25 100644 --- a/src/envs/error_handler.rs +++ b/src/envs/error_handler.rs @@ -4,11 +4,15 @@ use crate::util::message; pub const WRN_GENERAL: i32 = 0x00; pub const ERR_GENERAL: i32 = 0x01; -pub const ERR_FILE_NOT_FOUND: i32 = 0x02; -pub const ERR_MODULE_NOT_IMPLEMENTED: i32 = 0x03; -pub const ERR_ARGPARSE: i32 = 0x04; -pub const ERR_BINARY_NOT_FOUND: i32 = 0x05; -pub const ERR_OUTPUT_EXISTS: i32 = 0x06; +pub const ERR_FILE_NOT_FOUND: i32 = 0x10; +pub const ERR_FILE_INVALID: i32 = 0x11; +pub const ERR_BINARY_NOT_FOUND: i32 = 0x20; +pub const ERR_BINARY_NOT_EXECUTABLE: i32 = 0x21; +pub const ERR_BINARY_INVALID: i32 = 0x22; +pub const ERR_MODULE_NOT_IMPLEMENTED: i32 = 0x30; +pub const ERR_ARGPARSE: i32 = 0x40; +pub const ERR_OUTPUT_EXISTS: i32 = 0x50; + fn build_message(code: i32, passed_object: Option) -> String { let object = passed_object.unwrap_or_else(|| "".to_string()); @@ -16,10 +20,14 @@ fn build_message(code: i32, passed_object: Option) -> String { WRN_GENERAL => format!("Warning: {}", object), ERR_GENERAL => format!("Error: {}", object), ERR_FILE_NOT_FOUND => format!("File not found: {}", object), + ERR_FILE_INVALID => format!("Invalid file given: {}", object), + ERR_BINARY_NOT_FOUND => format!("Binary not found: {}", object), + ERR_BINARY_NOT_EXECUTABLE => format!("Binary not executable: {}", object), + ERR_BINARY_INVALID => format!("Invalid binary given: {}", object), ERR_MODULE_NOT_IMPLEMENTED => format!("Module not implemented: {}", object), ERR_ARGPARSE => format!("Argument parsing error: {}", object), - ERR_BINARY_NOT_FOUND => format!("Binary not found: {}", object), ERR_OUTPUT_EXISTS => format!("Output file already exists: {}; use -o to overwrite", object), + _ => "Unknown error".to_string(), } } diff --git a/src/envs/variables.rs b/src/envs/variables.rs index 577a668..45809cc 100644 --- a/src/envs/variables.rs +++ b/src/envs/variables.rs @@ -85,18 +85,20 @@ pub fn locate_encoder_py() -> String { } // binary paths -const VALID_BINARY: [&str; 7] = [ - "mmseqs", "foldseek", "mafft", "mafft-linsi", "foldmason", "iqtree", "fasttree", +pub const VALID_BINARY: [&str; 8] = [ + "mmseqs", "foldseek", "mafft", "mafft-linsi", "foldmason", "iqtree", "fasttree", "raxml" ]; pub struct Binary { name: String, pub path: String, + pub set: bool, } impl Binary { fn new(name: &str, path: &str) -> Self { Binary { name: name.to_string(), path: path.to_string(), + set: false, } } fn test(&self, args: Vec<&str>) -> bool { @@ -129,8 +131,10 @@ impl BinaryPaths { let mut split = line.split('='); let name = split.next().unwrap_or(""); let path = split.next().unwrap_or(""); + if path.len() == 0 { continue; } if let Some(&i) = self.map.get(name) { self.bin[i].path = path.to_string(); + self.bin[i].set = true; } } Ok(()) diff --git a/src/main.rs b/src/main.rs index c367a8c..b857fbf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,6 +44,9 @@ fn run(args: &parser::Args, bin: &var::BinaryPaths, test: bool) -> Result<(), Bo Some(parser::Commands::EasySearch { .. }) => { workflow::easy_search::run(args, bin).unwrap_or_else(|e| err::error(err::ERR_GENERAL, Some(e.to_string()))); } + Some(parser::Commands::Config { .. }) => { + modules::config::run(args, bin).unwrap_or_else(|e| err::error(err::ERR_GENERAL, Some(e.to_string()))); + }, /* Some(_) => { err::error(err::ERR_MODULE_NOT_IMPLEMENTED, std::env::args().nth(1)); } */ diff --git a/src/modules/config.rs b/src/modules/config.rs new file mode 100644 index 0000000..4467cf9 --- /dev/null +++ b/src/modules/config.rs @@ -0,0 +1,93 @@ +use std::os::unix::fs::MetadataExt; +use std::io::Write; +use crate::envs::error_handler as err; +use crate::envs::variables as var; +use crate::envs::variables::BinaryPaths; +use crate::util::command as cmd; +use crate::util::message as msg; +use crate::util::arg_parser::Args; +use color_print::cstr; + +fn task_check(bin: &BinaryPaths) -> Result<(), Box> { + msg::println_message(&format!("{}", cstr!(r#"System:"#)), 3); + msg::println_message(&format!("Unicore version: {}", var::VERSION), 3); + msg::println_message(&format!("OS: {}", std::env::consts::OS), 3); + msg::println_message(&format!("Threads: {}", var::threads()), 3); + println!(); + msg::println_message(&format!("{}", cstr!(r#"Dependencies:"#)), 3); + msg::println_message(&format!("MMseqs2: {} .. {}", + if let Some(&ref bin) = &bin.get("mmseqs") { if bin.set { bin.path.clone() } else { "Unset".to_string() } } else { "Undefined".to_string() }, + if let Some(&ref bin) = &bin.get("mmseqs") { if bin.set { if binary_run_test(&bin.path, "mmseqs") { cstr!(r#"ok"#) } else { cstr!(r#"no"#) } } else { cstr!(r#"n/a"#) } } else { cstr!(r#"n/a"#) }, + ), 3); + msg::println_message(&format!("Foldseek: {} .. {}", + if let Some(&ref bin) = &bin.get("foldseek") { if bin.set { bin.path.clone() } else { "Unset".to_string() } } else { "Undefined".to_string() }, + if let Some(&ref bin) = &bin.get("foldseek") { if bin.set { if binary_run_test(&bin.path, "foldseek") { cstr!(r#"ok"#) } else { cstr!(r#"no"#) } } else { cstr!(r#"n/a"#) } } else { cstr!(r#"n/a"#) }, + ), 3); + msg::println_message(&format!("FoldMason: {} .. {}", + if let Some(&ref bin) = &bin.get("foldmason") { if bin.set { bin.path.clone() } else { "Unset".to_string() } } else { "Undefined".to_string() }, + if let Some(&ref bin) = &bin.get("foldmason") { if bin.set { if binary_run_test(&bin.path, "foldmason") { cstr!(r#"ok"#) } else { cstr!(r#"no"#) } } else { cstr!(r#"n/a"#) } } else { cstr!(r#"n/a"#) }, + ), 3); + msg::println_message(&format!("MAFFT: {} .. {}", + if let Some(&ref bin) = &bin.get("mafft") { if bin.set { bin.path.clone() } else { "Unset".to_string() } } else { "Undefined".to_string() }, + if let Some(&ref bin) = &bin.get("mafft") { if bin.set { if binary_run_test(&bin.path, "mafft") { cstr!(r#"ok"#) } else { cstr!(r#"no"#) } } else { cstr!(r#"n/a"#) } } else { cstr!(r#"n/a"#) }, + ), 3); + msg::println_message(&format!("IQ-TREE: {} .. {}", + if let Some(&ref bin) = &bin.get("iqtree") { if bin.set { bin.path.clone() } else { "Unset".to_string() } } else { "Undefined".to_string() }, + if let Some(&ref bin) = &bin.get("iqtree") { if bin.set { if binary_run_test(&bin.path, "iqtree") { cstr!(r#"ok"#) } else { cstr!(r#"no"#) } } else { cstr!(r#"n/a"#) } } else { cstr!(r#"n/a"#) }, + ), 3); + msg::println_message(&format!("FastTree: {} .. {}", + if let Some(&ref bin) = &bin.get("fasttree") { if bin.set { bin.path.clone() } else { "Unset".to_string() } } else { "Undefined".to_string() }, + if let Some(&ref bin) = &bin.get("fasttree") { if bin.set { if binary_run_test(&bin.path, "fasttree") { cstr!(r#"ok"#) } else { cstr!(r#"no"#) } } else { cstr!(r#"n/a"#) } } else { cstr!(r#"n/a"#) }, + ), 3); + msg::println_message(&format!("RAxML: {} .. {}", + if let Some(&ref bin) = &bin.get("raxml") { if bin.set { bin.path.clone() } else { "Unset".to_string() } } else { "Undefined".to_string() }, + if let Some(&ref bin) = &bin.get("raxml") { if bin.set { if binary_run_test(&bin.path, "raxml") { cstr!(r#"ok"#) } else { cstr!(r#"no"#) } } else { cstr!(r#"n/a"#) } } else { cstr!(r#"n/a"#) }, + ), 3); + Ok(()) +} + +fn binary_run_test(path: &str, sw: &str) -> bool { + if !var::VALID_BINARY.contains(&sw) { return false; } + let mut test_command = std::process::Command::new(path); + let test_command = match sw { + "mmseqs" | "foldseek" | "foldmason" => test_command.arg("version"), + "mafft" | "mafft-linsi" | "iqtree" => test_command.arg("--version"), + "fasttree" => &mut test_command, + "raxml" => test_command.arg("-v"), + _ => return false, + }; + cmd::run_code(test_command) == 0 +} + +fn set_binary(bin: &BinaryPaths, path: &str, sw: &str) -> Result<(), Box> { + if !std::fs::File::open(path).is_ok() { err::error(err::ERR_BINARY_NOT_FOUND, Some(format!("{}", path))); } + if std::fs::metadata(path)?.is_dir() { err::error(err::ERR_FILE_INVALID, Some(format!("{}", path))); } + if std::fs::metadata(path)?.mode() & 0o111 == 0 { err::error(err::ERR_BINARY_NOT_EXECUTABLE, Some(format!("{}", path))); } + if !binary_run_test(path, sw) { err::error(err::ERR_BINARY_INVALID, Some(format!("{}", path))); } + + let path = std::fs::canonicalize(path)?.to_str().unwrap().to_string(); + msg::println_message(&format!("Setting dependency {} to {}...", sw, path), 3); + let mut cfg = std::fs::File::create(var::locate_path_cfg())?; + for &prog in var::VALID_BINARY.iter() { + if prog == sw { cfg.write_all(format!("{}={}\n", prog, path).as_bytes())?; } + else if bin.get(prog).is_none() || !bin.get(prog).unwrap().set { cfg.write_all(format!("#{}=\n", prog).as_bytes())?; } + else { cfg.write_all(format!("{}={}\n", prog, bin.get(prog).unwrap().path).as_bytes())?; } + } + msg::println_message(&"Done. Please run \"unicore config -c\" to check".to_string(), 3); + + Ok(()) +} + +pub fn run(args: &Args, bin: &BinaryPaths) -> Result<(), Box> { + if args.config_check.is_some() && args.config_check.unwrap() { task_check(bin)?; } + else if args.config_set_mmseqs.is_some() { set_binary(bin, args.config_set_mmseqs.clone().unwrap().as_str(), "mmseqs")?; } + else if args.config_set_foldseek.is_some() { set_binary(bin, args.config_set_foldseek.clone().unwrap().as_str(), "foldseek")?; } + else if args.config_set_foldmason.is_some() { set_binary(bin, args.config_set_foldmason.clone().unwrap().as_str(), "foldmason")?; } + else if args.config_set_mafft.is_some() { set_binary(bin, args.config_set_mafft.clone().unwrap().as_str(), "mafft")?; } + else if args.config_set_mafft_linsi.is_some() { set_binary(bin, args.config_set_mafft_linsi.clone().unwrap().as_str(), "mafft-linsi")?; } + else if args.config_set_iqtree.is_some() { set_binary(bin, args.config_set_iqtree.clone().unwrap().as_str(), "iqtree")?; } + else if args.config_set_fasttree.is_some() { set_binary(bin, args.config_set_fasttree.clone().unwrap().as_str(), "fasttree")?; } + else if args.config_set_raxml.is_some() { set_binary(bin, args.config_set_raxml.clone().unwrap().as_str(), "raxml")?; } + else { err::error(err::ERR_ARGPARSE, Some("No task specified".to_string())) }; + Ok(()) +} \ No newline at end of file diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 4d3c15b..23c1bd7 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -4,4 +4,5 @@ pub mod cluster; pub mod search; pub mod profile; pub mod tree; -pub mod genetree; \ No newline at end of file +pub mod genetree; +pub mod config; \ No newline at end of file diff --git a/src/util/arg_parser.rs b/src/util/arg_parser.rs index f3ffc19..512d9a9 100644 --- a/src/util/arg_parser.rs +++ b/src/util/arg_parser.rs @@ -362,6 +362,40 @@ pub enum Commands { #[arg(short='v', long, default_value="3")] verbosity: u8, }, + /// Runtime environment configuration + #[clap(arg_required_else_help = true, allow_hyphen_values = true)] + Config { + /// Check current environment configuration + #[arg(short='c', long)] + check: bool, + /// Set mmseqs binary path + #[arg(long)] + set_mmseqs: Option, + /// Set foldseek binary path + #[arg(long)] + set_foldseek: Option, + /// Set foldmason binary path + #[arg(long)] + set_foldmason: Option, + /// Set mafft binary path + #[arg(long)] + set_mafft: Option, + /// Set mafft-linsi binary path + #[arg(long)] + set_mafft_linsi: Option, + /// Set iqtree binary path + #[arg(long)] + set_iqtree: Option, + /// Set fasttree binary path + #[arg(long)] + set_fasttree: Option, + /// Set raxml binary path + #[arg(long)] + set_raxml: Option, + /// Verbosity (0: quiet, 1: +errors, 2: +warnings, 3: +info, 4: +debug) + #[arg(short='v', long, default_value="3")] + verbosity: u8, + }, } #[derive(Default)] @@ -419,6 +453,16 @@ pub struct Args { pub genetree_realign: Option, pub genetree_aligner: Option, pub genetree_aligner_options: Option>, + + pub config_check: Option, + pub config_set_mmseqs: Option, + pub config_set_foldseek: Option, + pub config_set_foldmason: Option, + pub config_set_mafft: Option, + pub config_set_mafft_linsi: Option, + pub config_set_iqtree: Option, + pub config_set_fasttree: Option, + pub config_set_raxml: Option, } fn own(path: &PathBuf) -> String { path.clone().to_string_lossy().into_owned() } impl Args { @@ -433,6 +477,7 @@ impl Args { Some(GeneTree { verbosity, .. }) => *verbosity, Some(EasyCore { verbosity, .. }) => *verbosity, Some(EasySearch { verbosity, .. }) => *verbosity, + Some(Config { verbosity, .. }) => *verbosity, _ => 3, }; let threads = match &args.command { @@ -641,6 +686,34 @@ impl Args { Some(GeneTree { threshold, .. }) => Some(*threshold), _ => None, }; + let config_check = match &args.command { + Some(Config { check, .. }) => Some(*check), _ => None, + }; + let config_set_mmseqs = match &args.command { + Some(Config { set_mmseqs, .. }) => match set_mmseqs { Some(p) => Some(own(p)), _ => None }, _ => None, + }; + let config_set_foldseek = match &args.command { + Some(Config { set_foldseek, .. }) => match set_foldseek { Some(p) => Some(own(p)), _ => None }, _ => None, + }; + let config_set_foldmason = match &args.command { + Some(Config { set_foldmason, .. }) => match set_foldmason { Some(p) => Some(own(p)), _ => None }, _ => None, + }; + let config_set_mafft = match &args.command { + Some(Config { set_mafft, .. }) => match set_mafft { Some(p) => Some(own(p)), _ => None }, _ => None, + }; + let config_set_mafft_linsi = match &args.command { + Some(Config { set_mafft_linsi, .. }) => match set_mafft_linsi { Some(p) => Some(own(p)), _ => None }, _ => None, + }; + let config_set_iqtree = match &args.command { + Some(Config { set_iqtree, .. }) => match set_iqtree { Some(p) => Some(own(p)), _ => None }, _ => None, + }; + let config_set_fasttree = match &args.command { + Some(Config { set_fasttree, .. }) => match set_fasttree { Some(p) => Some(own(p)), _ => None }, _ => None, + }; + let config_set_raxml = match &args.command { + Some(Config { set_raxml, .. }) => match set_raxml { Some(p) => Some(own(p)), _ => None }, _ => None, + }; + Args { command: args.command, version: args.version, threads, verbosity, createdb_input, createdb_output, createdb_model, createdb_keep, createdb_overwrite, createdb_max_len, createdb_gpu, createdb_use_python, createdb_use_foldseek, createdb_afdb_lookup, createdb_afdb_local, @@ -649,6 +722,7 @@ impl Args { cluster_input, cluster_output, cluster_tmp, cluster_keep_cluster_db, cluster_cluster_options, tree_db, tree_input, tree_output, tree_aligner, tree_tree_builder, tree_aligner_options, tree_tree_options, tree_threshold, genetree_input, genetree_names, genetree_tree_builder, genetree_tree_options, genetree_realign, genetree_aligner, genetree_aligner_options, genetree_threshold, + config_check, config_set_mmseqs, config_set_foldseek, config_set_foldmason, config_set_mafft, config_set_mafft_linsi, config_set_iqtree, config_set_fasttree, config_set_raxml, } } } \ No newline at end of file diff --git a/src/util/command.rs b/src/util/command.rs index c54a00a..169436a 100644 --- a/src/util/command.rs +++ b/src/util/command.rs @@ -23,6 +23,26 @@ pub fn run(cmd: &mut std::process::Command) { } } +pub fn run_code(cmd: &mut std::process::Command) -> i32 { + let cmd = cmd.stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null()); + let cmdstr = format!("{:?}", cmd).replace("\"", ""); + msg::println_message(&format!("Running command: {}", cmdstr), 4); + if let Ok(mut child) = cmd.spawn() { + let wait = child.wait(); + if let Ok(status) = wait { + if let Some(code) = status.code() { + code + } else { + 1 + } + } else { + 1 + } + } else { + 1 + } +} + pub fn _run_at(cmd: &mut std::process::Command, path: &std::path::Path) { let cmdstr = format!("{:?}", cmd); if let Ok(mut child) = cmd.current_dir(path).spawn() {