From e64ce1f4591bfe9893f5161a293a39ae934755ac Mon Sep 17 00:00:00 2001 From: Ian Wijma Date: Fri, 5 Apr 2024 21:03:44 +1100 Subject: [PATCH] Made the code execute the commands. --- src/commands/run.rs | 172 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 150 insertions(+), 22 deletions(-) diff --git a/src/commands/run.rs b/src/commands/run.rs index adbeed8..a5e3bd6 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -2,6 +2,10 @@ use std::collections::HashMap; use std::fmt::Debug; use std::fs::{canonicalize, read_to_string}; use std::path::{Path, PathBuf}; +use std::process::Command; +use std::thread; +use std::thread::JoinHandle; +use std::time::Instant; use clap::Args; use glob::glob; use globset::{Glob, GlobSetBuilder}; @@ -18,6 +22,9 @@ pub struct Arguments { pub fn run (arguments: &Arguments) -> Result<(), String> { let Arguments { entry, task_name } = arguments; + // Start the timer + let start_time = Instant::now(); + // Resolve the entry path let entry_config_path: PathBuf = resolve_config_path(entry)?; @@ -35,24 +42,143 @@ pub fn run (arguments: &Arguments) -> Result<(), String> { let config_structure: ConfigStructure = resolve_config_structure(&entry_config_path, configs)?; // Gather the tasks from the config - let task_structure: TaskStructure = resolve_task_structure(config_structure, task_name); - eprintln!("task_structure = {:?}", task_structure); + let ordered_tasks: OrderedTasks = resolve_task_order(config_structure, task_name)?; // Run the commands, one by one // > In the future this is configurable on the rask level and maybe on the config file level // > Initially it fails the whole command if one task fails, but will also be configurable in the future - // let task_exit: TaskExit = run_task_structure(task_structure); + let task_exit: TaskExit = run_task_structure(&ordered_tasks)?; + + let task_amount = ordered_tasks.len(); + let execution_time = start_time.elapsed().as_secs_f32(); + let formatted_execution_time = (execution_time * 100.0).round() / 100.0; + + match task_exit { + TaskExit::SUCCESS => println!("{}", format!("Successfully executed {} tasks within {} seconds", task_amount, formatted_execution_time)), + TaskExit::FAILURE => println!("{}", format!("Failed after executing {} tasks within {} seconds", task_amount, formatted_execution_time)), + } Ok(()) } -#[derive(Debug, Clone)] -struct TaskStructure { +fn run_task_structure(ordered_tasks: &OrderedTasks) -> Result { + let highest_order = find_highest_order(&ordered_tasks)?; + for order in (0..=highest_order).rev() { + match run_task_order(&ordered_tasks, order) { + Ok(_) => {} + Err(err) => { + println!("{}", err); + return Ok(TaskExit::FAILURE); + } + } + } + + Ok(TaskExit::SUCCESS) } -fn resolve_task_structure(config_structure: ConfigStructure, task_name: &String) -> TaskStructure { - todo!() +fn run_task_order(ordered_tasks: &OrderedTasks, order: u64) -> Result<(), String> { + let mut tasks: Vec<&Task> = vec![]; + for ordered_task in ordered_tasks { + let OrderedTask { task, order: task_order } = ordered_task; + if *task_order == order { + tasks.push(task); + } + } + + let mut task_threads: Vec> = vec![]; + for task in tasks { + let task_thread = execute_task(task)?; + task_threads.push(task_thread); + } + + for task_thread in task_threads { + if let Ok(success) = task_thread.join() { + if !success { + return Err("Command execution failed.".to_string()); + } + } else { + return Err("Thread panicked.".to_string()); + } + } + + Ok(()) +} + +fn execute_task(task: &Task) -> Result, String> { + let task_thread = thread::spawn({ + let task = task.clone(); + move || { + let status = Command::new("sh") + .arg("-c") + .arg(&task.command) + .current_dir(&task.directory) + .status() + .expect("Failed to execute command"); + status.success() + } + }); + + Ok(task_thread) +} + +fn find_highest_order(ordered_tasks: &OrderedTasks) -> Result { + let mut highest_order: u64 = 0u64; + + for ordered_task in ordered_tasks { + if ordered_task.order > highest_order { + highest_order = ordered_task.order; + } + } + + Ok(highest_order) +} + +#[derive(Debug, Clone)] +enum TaskExit { + SUCCESS, + FAILURE +} + +#[derive(Debug, Clone)] +struct Task { + command: String, + directory: PathBuf, +} + +#[derive(Debug, Clone)] +struct OrderedTask { + task: Task, + order: u64 +} + +type OrderedTasks = Vec; + +fn resolve_task_order(config_structure: ConfigStructure, task_name: &String) -> Result { + let mut ordered_tasks: OrderedTasks = vec![]; + + order_tasks(&mut ordered_tasks, config_structure, task_name, 0); + + Ok(ordered_tasks) +} + +fn order_tasks(ordered_tasks: &mut OrderedTasks, config_structure: ConfigStructure, task_name: &String, index: u64) { + let ConfigStructure { config, children } = config_structure; + let Config { tasks, dir_path, .. } = config; + match tasks.get(task_name) { + None => {} + Some(&ref config_task) => ordered_tasks.push(OrderedTask{ + task: Task { + command: config_task.content.clone(), + directory: dir_path, + }, + order: index + }) + } + + for child in children { + order_tasks(ordered_tasks, child, task_name, index+1); + } } #[derive(Debug, Clone)] @@ -65,7 +191,7 @@ fn resolve_config_structure(entry_config_path: &PathBuf, configs: Vec) - let mut path_map: HashMap = HashMap::new(); for config in configs { - path_map.insert(config.clone().path, config); + path_map.insert(config.clone().file_path, config); } let config_structure: ConfigStructure = construct_config_structure(entry_config_path, &path_map)?; @@ -147,7 +273,8 @@ struct Config { name: String, task_engine: TaskEngine, tasks: HashMap, - path: PathBuf, + file_path: PathBuf, + dir_path: PathBuf, directories: ConfigDirectories, } @@ -163,17 +290,17 @@ fn parse_config_files(config_files: Vec) -> Result, Stri } fn parse_config_file(config_file: ConfigFile) -> Result { - let ConfigFile { name, tasks: config_file_tasks, _file_path: file_path, _dir_path: dir_path, directories, task_engine } = config_file; + let ConfigFile { name, tasks: config_file_tasks, __file_path: file_path, __dir_path: dir_path, directories, task_engine } = config_file; let tasks: ConfigTasks = match task_engine { - TaskEngine::COMPOSER => parse_composer_tasks(dir_path)?, - TaskEngine::NPM => parse_node_tasks(dir_path, "npm")?, - TaskEngine::YARN => parse_node_tasks(dir_path, "yarn")?, - TaskEngine::PNPM => parse_node_tasks(dir_path, "pnmp")?, + TaskEngine::COMPOSER => parse_composer_tasks(&dir_path)?, + TaskEngine::NPM => parse_node_tasks(&dir_path, "npm")?, + TaskEngine::YARN => parse_node_tasks(&dir_path, "yarn")?, + TaskEngine::PNPM => parse_node_tasks(&dir_path, "pnpm")?, TaskEngine::NONE => parse_config_tasks(config_file_tasks)?, }; - let config: Config = Config { name, tasks, path: file_path, directories, task_engine }; + let config: Config = Config { name, tasks, file_path, dir_path, directories, task_engine }; Ok(config) } @@ -184,7 +311,7 @@ struct PackageJsonFile { scripts: HashMap, } -fn parse_node_tasks(dir_path: PathBuf, prefix: &str) -> Result { +fn parse_node_tasks(dir_path: &PathBuf, prefix: &str) -> Result { let mut file_path = dir_path.clone(); file_path.push("package.json"); let content = read_file_content(file_path)?; @@ -215,7 +342,7 @@ struct ComposerJsonFile { scripts: HashMap, } -fn parse_composer_tasks(dir_path: PathBuf) -> Result { +fn parse_composer_tasks(dir_path: &PathBuf) -> Result { let mut file_path = dir_path.clone(); file_path.push("composer.json"); let content = read_file_content(file_path)?; @@ -265,7 +392,7 @@ fn discover_config_paths(path: &PathBuf) -> Result, String> { // Read config let mut path_stack: Vec = vec![path.clone()]; while !path_stack.is_empty() { - let ConfigFile { directories, _file_path, .. } = read_config_file(path_stack.pop().unwrap())?; + let ConfigFile { directories, __file_path: _file_path, .. } = read_config_file(path_stack.pop().unwrap())?; // Extract directories let config_directory = _file_path.parent().ok_or("Failed to get parent directory")?; @@ -318,10 +445,11 @@ struct ConfigFile { directories: ConfigDirectories, #[serde(default)] tasks: ConfigFileTasks, + // The following fields are not part of the yaml file. #[serde(default)] - _file_path: PathBuf, + __file_path: PathBuf, #[serde(default)] - _dir_path: PathBuf, + __dir_path: PathBuf, } fn read_config_file(path: PathBuf) -> Result { @@ -329,8 +457,8 @@ fn read_config_file(path: PathBuf) -> Result { let mut config_file: ConfigFile = serde_yaml::from_str(&content).expect(format!("Failed to parse YAML from \"{:?}\"", path).as_str()); - config_file._file_path = path.clone(); - config_file._dir_path = path.parent().unwrap().to_path_buf(); + config_file.__file_path = path.clone(); + config_file.__dir_path = path.parent().unwrap().to_path_buf(); Ok(config_file) }