From c49defbf1708c8df722d1607f592eab1ce696ce3 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 31 May 2024 16:34:47 +0200 Subject: [PATCH 01/73] chore: setting up the base ideas of the refactor and the clean-up --- zork++/src/lib/cache/mod.rs | 314 ++++++++++++---------- zork++/src/lib/cli/output/commands.rs | 5 +- zork++/src/lib/lib.rs | 2 +- zork++/src/lib/project_model/sourceset.rs | 2 +- 4 files changed, 172 insertions(+), 151 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 03f42cbe..eb3f9ec0 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -88,11 +88,25 @@ pub struct ZorkCache { pub compiler: CppCompiler, pub last_program_execution: DateTime, pub compilers_metadata: CompilersMetadata, - pub last_generated_commands: HashMap>, - pub last_generated_linker_commands: HashMap, - pub generated_commands: Vec, + pub last_generated_commands: HashMap>, // TODO remove it. Is only valid for translate our cache args fmt + pub last_generated_linker_commands: HashMap, // TODO set it with the generated_commands + pub generated_commands: Vec, // TODO meter todo aquí + // TODO meter al status ::Cached un valor dentro que indica cacheado desde hace cuanto? + // TODO meter un unique hash para el 'is_file_cached', para evitar matchear strings? Se le puede asignar un hash único desde + // la creación del project_model => ESO implica cachear el project model, ojo, no nos pasemos de listos, que solo se le puede + // poner a los que NO estén ya trackeados por el project model de Zork } +// TODO la idea buena. Diferentes niveles de -c, por ejemplo -c No vuelve a generar los comandos PERO los manda a compilar +// y -cc sería un clear_cache + +// ORDEN: +// 1 - Arreglar el formato de entradas, para que tenga todos los datos del json del compile_commands +// 2 - Zumbarse los últimos commands +// 3 - pre_tasks no está mal, pero mejor un named que refleje las librerías standard +// 4 - cachear el project model +// 5 - meter la última del jota-son no del tomto-ml y testear rendimiento serializando todo tipos owned + impl ZorkCache { /// Returns a [`Option`] of [`CommandDetails`] if the file is persisted already in the cache pub fn is_file_cached(&self, path: impl AsRef) -> Option<&CommandDetail> { @@ -231,145 +245,6 @@ impl ZorkCache { are_new_commands.iter().any(|b| *b) } - /// If Windows is the current OS, and the compiler is MSVC, then we will try - /// to locate the path of the `vcvars64.bat` script that will set a set of environmental - /// variables that are required to work effortlessly with the Microsoft's compiler. - /// - /// After such effort, we will dump those env vars to a custom temporary file where every - /// env var is registered there in a key-value format, so we can load it into the cache and - /// run this process once per new cache created (cache action 1) - fn load_msvc_metadata(&mut self, program_data: &ZorkModel<'_>) -> Result<()> { - let msvc = &mut self.compilers_metadata.msvc; - - if msvc.dev_commands_prompt.is_none() { - let compiler = program_data.compiler.cpp_compiler; - - msvc.dev_commands_prompt = utils::fs::find_file( - Path::new(constants::MSVC_REGULAR_BASE_PATH), - constants::MS_ENV_VARS_BAT, - ) - .map(|walkdir_entry| { - walkdir_entry.path().to_string_lossy().replace( - constants::MSVC_REGULAR_BASE_PATH, - constants::MSVC_REGULAR_BASE_SCAPED_PATH, - ) - }); - let output = std::process::Command::new(constants::WIN_CMD) - .arg("/c") - .arg(msvc.dev_commands_prompt.as_ref().ok_or_eyre("Zork++ wasn't unable to find the VS env vars")?) - .arg("&&") - .arg("set") - .output() - .with_context(|| "Unable to load MSVC pre-requisites. Please, open an issue with the details on upstream")?; - - msvc.env_vars = Self::load_env_vars_from_cmd_output(&output.stdout)?; - // Cloning the useful ones for quick access at call site - msvc.compiler_version = msvc.env_vars.get("VisualStudioVersion").cloned(); - - let vs_stdlib_path = - Path::new(msvc.env_vars.get("VCToolsInstallDir").unwrap()).join("modules"); - msvc.vs_stdlib_path = Some(SourceFile { - path: vs_stdlib_path.clone(), - file_stem: String::from("std"), - extension: compiler.get_default_module_extension().to_string(), - }); - msvc.vs_c_stdlib_path = Some(SourceFile { - path: vs_stdlib_path, - file_stem: String::from("std.compat"), - extension: compiler.get_default_module_extension().to_string(), - }); - let modular_stdlib_byproducts_path = Path::new(&program_data.build.output_dir) - .join(compiler.as_ref()) - .join("modules") - .join("std") // folder - .join("std"); // filename - - // Saving the paths to the precompiled bmi and obj files of the MSVC std implementation - // that will be used to reference the build of the std as a module - msvc.stdlib_bmi_path = - modular_stdlib_byproducts_path.with_extension(compiler.get_typical_bmi_extension()); - msvc.stdlib_obj_path = - modular_stdlib_byproducts_path.with_extension(compiler.get_obj_file_extension()); - - let c_modular_stdlib_byproducts_path = modular_stdlib_byproducts_path; - let compat = String::from("compat."); // TODO: find a better way - msvc.c_stdlib_bmi_path = c_modular_stdlib_byproducts_path - .with_extension(compat.clone() + compiler.get_typical_bmi_extension()); - msvc.c_stdlib_obj_path = c_modular_stdlib_byproducts_path - .with_extension(compat + compiler.get_obj_file_extension()); - } - - Ok(()) - } - - /// Convenient helper to manipulate and store the environmental variables as result of invoking - /// the Windows `SET` cmd command - fn load_env_vars_from_cmd_output(stdout: &[u8]) -> Result> { - let env_vars_str = std::str::from_utf8(stdout)?; - let filter = Regex::new(r"^[a-zA-Z_]+$").unwrap(); - - let mut env_vars: HashMap = HashMap::new(); - for line in env_vars_str.lines() { - // Parse the key-value pair from each line - let mut parts = line.splitn(2, '='); - let key = parts.next().expect("Failed to get key").trim(); - - if filter.is_match(key) { - let value = parts.next().unwrap_or_default().trim().to_string(); - env_vars.insert(key.to_string(), value); - } - } - - Ok(env_vars) - } - - /// Looks for the already precompiled `GCC` or `Clang` system headers, - /// to avoid recompiling them on every process - /// NOTE: This feature should be deprecated an therefore, removed from Zork++ when GCC and - /// Clang fully implement the required procedures to build the C++ std library as a module - fn track_system_modules<'a>( - program_data: &'a ZorkModel<'_>, - ) -> impl Iterator + 'a { - let root = if program_data.compiler.cpp_compiler == CppCompiler::GCC { - Path::new(GCC_CACHE_DIR).to_path_buf() - } else { - program_data - .build - .output_dir - .join("clang") - .join("modules") - .join("interfaces") - }; - - WalkDir::new(root) - .into_iter() - .filter_map(Result::ok) - .filter(|file| { - if file - .metadata() - .expect("Error retrieving metadata") - .is_file() - { - program_data - .modules - .sys_modules - .iter() - .any(|sys_mod| file.file_name().to_str().unwrap().starts_with(sys_mod)) - } else { - false - } - }) - .map(|dir_entry| { - dir_entry - .file_name() - .to_str() - .unwrap() - .split('.') - .collect::>()[0] - .to_string() - }) - } - fn normalize_execution_result_status( &self, module_command_line: &SourceCommandLine, @@ -429,6 +304,53 @@ impl ZorkCache { CppCompiler::GCC => &self.compilers_metadata.gcc.env_vars, } } + + /// Looks for the already precompiled `GCC` or `Clang` system headers, + /// to avoid recompiling them on every process + /// NOTE: This feature should be deprecated an therefore, removed from Zork++ when GCC and + /// Clang fully implement the required procedures to build the C++ std library as a module + fn track_system_modules<'a>( + program_data: &'a ZorkModel<'_>, + ) -> impl Iterator + 'a { + let root = if program_data.compiler.cpp_compiler == CppCompiler::GCC { + Path::new(GCC_CACHE_DIR).to_path_buf() + } else { + program_data + .build + .output_dir + .join("clang") + .join("modules") + .join("interfaces") + }; + + WalkDir::new(root) + .into_iter() + .filter_map(Result::ok) + .filter(|file| { + if file + .metadata() + .expect("Error retrieving metadata") + .is_file() + { + program_data + .modules + .sys_modules + .iter() + .any(|sys_mod| file.file_name().to_str().unwrap().starts_with(sys_mod)) + } else { + false + } + }) + .map(|dir_entry| { + dir_entry + .file_name() + .to_str() + .unwrap() + .split('.') + .collect::>()[0] + .to_string() + }) + } } #[derive(Deserialize, Serialize, Debug, Default, Clone)] @@ -474,9 +396,6 @@ pub struct CompilersMetadata { pub system_modules: Vec, // TODO: This hopefully will dissappear soon } -// TODO: review someday how to better structure the metadata per compiler -// and generalize this structures - #[derive(Deserialize, Serialize, Debug, Default, Clone)] pub struct MsvcMetadata { pub compiler_version: Option, @@ -508,3 +427,108 @@ pub struct ClangMetadata { pub struct GccMetadata { pub env_vars: EnvVars, } + +/// Helper procedures to process cache data for Microsoft's MSVC +mod msvc { + use std::collections::HashMap; + use std::path::Path; + use color_eyre::eyre::{Context, OptionExt}; + use regex::Regex; + use crate::cache::ZorkCache; + use crate::project_model::sourceset::SourceFile; + use crate::project_model::ZorkModel; + use crate::utils; + use crate::utils::constants; + + /// If Windows is the current OS, and the compiler is MSVC, then we will try + /// to locate the path of the `vcvars64.bat` script that will set a set of environmental + /// variables that are required to work effortlessly with the Microsoft's compiler. + /// + /// After such effort, we will dump those env vars to a custom temporary file where every + /// env var is registered there in a key-value format, so we can load it into the cache and + /// run this process once per new cache created (cache action 1) + fn load_metadata(cache: &mut ZorkCache, program_data: &ZorkModel<'_>) -> color_eyre::Result<()> { + let msvc = &mut cache.compilers_metadata.msvc; + + if msvc.dev_commands_prompt.is_none() { + let compiler = program_data.compiler.cpp_compiler; + + msvc.dev_commands_prompt = utils::fs::find_file( + Path::new(constants::MSVC_REGULAR_BASE_PATH), + constants::MS_ENV_VARS_BAT, + ) + .map(|walkdir_entry| { + walkdir_entry.path().to_string_lossy().replace( + constants::MSVC_REGULAR_BASE_PATH, + constants::MSVC_REGULAR_BASE_SCAPED_PATH, + ) + }); + let output = std::process::Command::new(constants::WIN_CMD) + .arg("/c") + .arg(msvc.dev_commands_prompt.as_ref().ok_or_eyre("Zork++ wasn't unable to find the VS env vars")?) + .arg("&&") + .arg("set") + .output() + .with_context(|| "Unable to load MSVC pre-requisites. Please, open an issue with the details on upstream")?; + + msvc.env_vars = Self::load_env_vars_from_cmd_output(&output.stdout)?; + // Cloning the useful ones for quick access at call site + msvc.compiler_version = msvc.env_vars.get("VisualStudioVersion").cloned(); + + let vs_stdlib_path = + Path::new(msvc.env_vars.get("VCToolsInstallDir").unwrap()).join("modules"); + msvc.vs_stdlib_path = Some(SourceFile { + path: vs_stdlib_path.clone(), + file_stem: String::from("std"), + extension: compiler.get_default_module_extension().to_string(), + }); + msvc.vs_c_stdlib_path = Some(SourceFile { + path: vs_stdlib_path, + file_stem: String::from("std.compat"), + extension: compiler.get_default_module_extension().to_string(), + }); + let modular_stdlib_byproducts_path = Path::new(&program_data.build.output_dir) + .join(compiler.as_ref()) + .join("modules") + .join("std") // folder + .join("std"); // filename + + // Saving the paths to the precompiled bmi and obj files of the MSVC std implementation + // that will be used to reference the build of the std as a module + msvc.stdlib_bmi_path = + modular_stdlib_byproducts_path.with_extension(compiler.get_typical_bmi_extension()); + msvc.stdlib_obj_path = + modular_stdlib_byproducts_path.with_extension(compiler.get_obj_file_extension()); + + let c_modular_stdlib_byproducts_path = modular_stdlib_byproducts_path; + let compat = String::from("compat."); // TODO: find a better way + msvc.c_stdlib_bmi_path = c_modular_stdlib_byproducts_path + .with_extension(compat.clone() + compiler.get_typical_bmi_extension()); + msvc.c_stdlib_obj_path = c_modular_stdlib_byproducts_path + .with_extension(compat + compiler.get_obj_file_extension()); + } + + Ok(()) + } + + /// Convenient helper to manipulate and store the environmental variables as result of invoking + /// the Windows `SET` cmd command + fn load_env_vars_from_cmd_output(stdout: &[u8]) -> color_eyre::Result> { + let env_vars_str = std::str::from_utf8(stdout)?; + let filter = Regex::new(r"^[a-zA-Z_]+$").unwrap(); + + let mut env_vars: HashMap = HashMap::new(); + for line in env_vars_str.lines() { + // Parse the key-value pair from each line + let mut parts = line.splitn(2, '='); + let key = parts.next().expect("Failed to get key").trim(); + + if filter.is_match(key) { + let value = parts.next().unwrap_or_default().trim().to_string(); + env_vars.insert(key.to_string(), value); + } + } + + Ok(env_vars) + } +} diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index d1b5f94a..28fbda7f 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -33,10 +33,6 @@ pub fn run_generated_commands( log::info!("Proceeding to execute the generated commands..."); let compiler = commands.compiler; - /* for pre_task in &commands.pre_tasks { - execute_command(compiler, program_data, pre_task, cache)?; - } */ - for sys_module in &commands.system_modules { // TODO: will be deprecated soon, hopefully execute_command(compiler, program_data, sys_module.1, cache)?; @@ -151,6 +147,7 @@ pub struct SourceCommandLine<'a> { pub args: Arguments<'a>, pub processed: bool, pub execution_result: CommandExecutionResult, + // TODO an enum with the Kind OF TU that is generating this scl? } impl<'a> SourceCommandLine<'a> { diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 3ba671ec..e9074e61 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -82,7 +82,7 @@ pub mod worker { let config = config_file::zork_cfg_from_file(raw_file.as_str()) .with_context(|| "Could not parse configuration file")?; let program_data = build_model(&config, cli_args, &abs_project_root)?; - create_output_directory(&program_data)?; + create_output_directory(&program_data)?; // TODO avoid this call without check if exists let cache = cache::load(&program_data, cli_args) .with_context(|| "Unable to load the Zork++ cache")?; diff --git a/zork++/src/lib/project_model/sourceset.rs b/zork++/src/lib/project_model/sourceset.rs index bba7313f..d0945697 100644 --- a/zork++/src/lib/project_model/sourceset.rs +++ b/zork++/src/lib/project_model/sourceset.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::cli::output::arguments::Argument; // Since every file on the system has a path, this acts as a cheap conceptual -// conversion to unifify PATH querying operations over anything that can be +// conversion to unify PATH querying operations over anything that can be // saved on a persistence system with an access route pub trait File { fn get_path(&self) -> PathBuf; From 82cef53d6e41c2dc213c1a374a3ceec0bf760fc6 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sat, 1 Jun 2024 14:04:39 +0200 Subject: [PATCH 02/73] feat: Moving towards the owned data processing model feat: removed the cache last generated commands, so they're one only one entity feat: reworked the conversion from our cache to the compile_commands.json format feat: unified the serialization and serialization entities. The intermediate ones used to clone data from the non-owned version of the cache has been deleted, and the SourceCommandLine now only works with owned data, so there's no need anymore to use those intermediate data mappers (that already was cloning from borrowed data) --- zork++/src/lib/bounds/mod.rs | 2 +- zork++/src/lib/cache/compile_commands.rs | 41 ++-- zork++/src/lib/cache/mod.rs | 245 ++++++++------------- zork++/src/lib/cli/output/arguments.rs | 96 ++++---- zork++/src/lib/cli/output/commands.rs | 103 +++++---- zork++/src/lib/compiler/mod.rs | 153 ++++++------- zork++/src/lib/project_model/compiler.rs | 4 +- zork++/src/lib/project_model/executable.rs | 4 +- zork++/src/lib/project_model/mod.rs | 2 +- zork++/src/lib/project_model/modules.rs | 4 +- zork++/src/lib/project_model/sourceset.rs | 2 +- zork++/src/lib/project_model/tests.rs | 10 +- zork++/src/lib/utils/reader.rs | 2 +- 13 files changed, 302 insertions(+), 366 deletions(-) diff --git a/zork++/src/lib/bounds/mod.rs b/zork++/src/lib/bounds/mod.rs index 832253d5..90315581 100644 --- a/zork++/src/lib/bounds/mod.rs +++ b/zork++/src/lib/bounds/mod.rs @@ -8,7 +8,7 @@ use crate::{cli::output::arguments::Argument, project_model::sourceset::SourceSe /// Bound for the user defined arguments that are passed to the compiler pub trait ExtraArgs<'a> { - fn extra_args(&'a self) -> &'a [Argument<'a>]; + fn extra_args(&'a self) -> &'a [Argument]; } /// Contracts for the executable operations diff --git a/zork++/src/lib/cache/compile_commands.rs b/zork++/src/lib/cache/compile_commands.rs index 59fe83fc..273cd697 100644 --- a/zork++/src/lib/cache/compile_commands.rs +++ b/zork++/src/lib/cache/compile_commands.rs @@ -1,4 +1,6 @@ use crate::cache::ZorkCache; +use crate::cli::output::arguments::Arguments; +use crate::cli::output::commands::SourceCommandLine; use crate::utils; use crate::utils::constants::COMPILATION_DATABASE; use color_eyre::eyre::{Context, Result}; @@ -11,9 +13,12 @@ use std::path::{Path, PathBuf}; /// the generated commands for the translation units pub(crate) fn map_generated_commands_to_compilation_db(cache: &ZorkCache) -> Result<()> { log::trace!("Generating the compilation database..."); - let mut compilation_db_entries = Vec::with_capacity(cache.last_generated_commands.len()); + let generated_commands = cache.get_all_commands_iter(); + let mut compilation_db_entries: Vec = + Vec::with_capacity(cache.count_total_generated_commands()); + // compilation_db_entries.push(latest_commands.linker) - for command in cache.last_generated_commands.iter() { + for command in generated_commands { compilation_db_entries.push(CompileCommands::from(command)); } @@ -30,22 +35,30 @@ pub(crate) fn map_generated_commands_to_compilation_db(cache: &ZorkCache) -> Res /// to the `compile_commands.json` compilation database file #[derive(Serialize, Debug, Default, Clone)] pub struct CompileCommands { - pub directory: String, + pub directory: PathBuf, pub file: String, - pub arguments: Vec, + pub arguments: Arguments, } -impl From<(&'_ PathBuf, &'_ Vec)> for CompileCommands { - fn from(value: (&PathBuf, &Vec)) -> Self { - let dir = value.0.parent().unwrap_or(Path::new(".")); - let mut file = value.0.file_stem().unwrap_or_default().to_os_string(); - file.push("."); - file.push(value.0.extension().unwrap_or_default()); - +impl From<&SourceCommandLine> for CompileCommands { + fn from(value: &SourceCommandLine) -> Self { + let value = value.clone(); Self { - directory: dir.to_str().unwrap_or_default().to_string(), - file: file.to_str().unwrap_or_default().to_string(), - arguments: value.1.clone(), + directory: value.directory, + file: value.filename, + arguments: value.args, } } } + +// TODO review how the linker command line must be specified for the compile_commands.json +// impl From<&LinkerCommandLine> for CompileCommands { +// fn from(value: &LinkerCommandLine) -> Self { +// let value = value.clone(); +// Self { +// directory: value.directory, +// file: value.filename, +// arguments: value.args, +// } +// } +// } diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index eb3f9ec0..c8400660 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -3,10 +3,10 @@ pub mod compile_commands; use chrono::{DateTime, Utc}; -use color_eyre::eyre::OptionExt; use color_eyre::{eyre::Context, Result}; -use regex::Regex; + use std::collections::HashMap; +use std::fmt::Debug; use std::{ fs, fs::File, @@ -45,7 +45,7 @@ pub fn load(program_data: &ZorkModel<'_>, cli_args: &CliArgs) -> Result, cli_args: &CliArgs) -> Result, cache: &mut ZorkCache, - commands: Commands<'_>, + commands: Commands, test_mode: bool, ) -> Result<()> { let cache_path = &program_data @@ -83,43 +83,28 @@ pub fn save( .with_context(move || "Error saving data to the Zork++ cache") } -#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct ZorkCache { pub compiler: CppCompiler, pub last_program_execution: DateTime, pub compilers_metadata: CompilersMetadata, - pub last_generated_commands: HashMap>, // TODO remove it. Is only valid for translate our cache args fmt - pub last_generated_linker_commands: HashMap, // TODO set it with the generated_commands - pub generated_commands: Vec, // TODO meter todo aquí - // TODO meter al status ::Cached un valor dentro que indica cacheado desde hace cuanto? - // TODO meter un unique hash para el 'is_file_cached', para evitar matchear strings? Se le puede asignar un hash único desde - // la creación del project_model => ESO implica cachear el project model, ojo, no nos pasemos de listos, que solo se le puede - // poner a los que NO estén ya trackeados por el project model de Zork + pub generated_commands: Vec, } -// TODO la idea buena. Diferentes niveles de -c, por ejemplo -c No vuelve a generar los comandos PERO los manda a compilar -// y -cc sería un clear_cache - -// ORDEN: -// 1 - Arreglar el formato de entradas, para que tenga todos los datos del json del compile_commands -// 2 - Zumbarse los últimos commands -// 3 - pre_tasks no está mal, pero mejor un named que refleje las librerías standard -// 4 - cachear el project model -// 5 - meter la última del jota-son no del tomto-ml y testear rendimiento serializando todo tipos owned - impl ZorkCache { /// Returns a [`Option`] of [`CommandDetails`] if the file is persisted already in the cache - pub fn is_file_cached(&self, path: impl AsRef) -> Option<&CommandDetail> { - let last_iteration_details = self.generated_commands.last(); - - if let Some(last_iteration) = last_iteration_details { - return last_iteration - .interfaces - .iter() - .chain(last_iteration.implementations.iter()) - .chain(last_iteration.sources.iter()) - .find(|comm_det| comm_det.file_path().eq(path.as_ref())); - } + pub fn is_file_cached(&self, _path: impl AsRef) -> Option<&CommandDetail> { + // let last_iteration_details = self.generated_commands.last(); + + // TODO: what a cost. We need to join them for every iteration and every file + // if let Some(last_iteration) = last_iteration_details { + // return last_iteration + // .interfaces + // .iter() + // .chain(last_iteration.implementations.iter()) + // .chain(last_iteration.sources.iter()) + // .find(|comm_det| comm_det.file_path().eq(path.as_ref())); + // } None } @@ -127,7 +112,7 @@ impl ZorkCache { pub fn run_tasks(&mut self, program_data: &ZorkModel<'_>) -> Result<()> { let compiler = program_data.compiler.cpp_compiler; if cfg!(target_os = "windows") && compiler == CppCompiler::MSVC { - self.load_msvc_metadata(program_data)? + msvc::load_metadata(self, program_data)? } if compiler != CppCompiler::MSVC { @@ -140,13 +125,13 @@ impl ZorkCache { } /// Runs the tasks just before end the program and save the cache - pub fn run_final_tasks( + fn run_final_tasks( &mut self, program_data: &ZorkModel<'_>, - mut commands: Commands<'_>, + commands: Commands, test_mode: bool, ) -> Result<()> { - if self.save_generated_commands(&mut commands, program_data, test_mode) + if self.save_generated_commands(commands, program_data, test_mode) && program_data.project.compilation_db { compile_commands::map_generated_commands_to_compilation_db(self)?; @@ -164,88 +149,40 @@ impl ZorkCache { Ok(()) } + /// Stores the generated commands for the process in the Cache. + /// ### Return: + /// a boolean indicating whether there's new generated commands (non cached), so + /// the compile commands must be regenerated fn save_generated_commands( &mut self, - commands: &mut Commands<'_>, - model: &ZorkModel, - test_mode: bool, + commands: Commands, + _model: &ZorkModel, + _test_mode: bool, // TODO: tests linker cmd? ) -> bool { log::debug!("Storing in the cache the last generated command lines..."); self.compiler = commands.compiler; - let process_no = if !self.generated_commands.is_empty() { - self.generated_commands.last().unwrap().cached_process_num + 1 + let _process_no = if !self.generated_commands.is_empty() { + // TODO: do we now need this one? + // self.generated_commands.last().unwrap().cached_process_num + 1 + 0 } else { 1 }; - let mut commands_details = CommandsDetails { - cached_process_num: process_no, - generated_at: Utc::now(), - interfaces: Vec::with_capacity(commands.interfaces.len()), - implementations: Vec::with_capacity(commands.implementations.len()), - sources: Vec::with_capacity(commands.sources.len()), - pre_tasks: Vec::with_capacity(commands.pre_tasks.len()), - main: MainCommandLineDetail::default(), - }; + // TODO missing the one that determines if there's a new compilation database that must be generated + // something like and iter that counts if at least one has been modified ?? + // let at_least_one_changed = commands. + self.generated_commands.push(commands); - let mut are_new_commands = Vec::with_capacity(4); - let pre_tasks_has_new_commands = self.extend_collection_of_source_file_details( - model, - &mut commands_details.pre_tasks, - &mut commands.pre_tasks, - commands.compiler, - ); - are_new_commands.push(pre_tasks_has_new_commands); - let interfaces_has_new_commands = self.extend_collection_of_source_file_details( - model, - &mut commands_details.interfaces, - &mut commands.interfaces, - commands.compiler, - ); - are_new_commands.push(interfaces_has_new_commands); - let implementations_has_new_commands = self.extend_collection_of_source_file_details( - model, - &mut commands_details.implementations, - &mut commands.implementations, - commands.compiler, - ); - are_new_commands.push(implementations_has_new_commands); - let sources_has_new_commands = self.extend_collection_of_source_file_details( - model, - &mut commands_details.sources, - &mut commands.sources, - commands.compiler, - ); - are_new_commands.push(sources_has_new_commands); - - commands_details.main = MainCommandLineDetail { - files: commands.main.sources_paths.clone(), - execution_result: commands.main.execution_result, - command: commands - .main - .args - .iter() - .map(|arg| arg.value.to_string()) - .collect::>() - .join(" "), - }; - - let named_target = if test_mode { "test_main" } else { "main" }; - self.last_generated_linker_commands - .entry(PathBuf::from(named_target)) - .and_modify(|e| { - if !(*e).eq(&commands_details.main.command) { - e.clone_from(&commands_details.main.command) - } - }) - .or_insert(commands_details.main.command.clone()); - - self.generated_commands.push(commands_details); - - are_new_commands.iter().any(|b| *b) + self.get_all_commands_iter()// TODO: Review the conditions and ensure that are the ones that we're looking for + .any(|cmd| cmd.processed || cmd.execution_result.eq(&CommandExecutionResult::Success)) + + // INSTEAD OF THIS, we just can return an Optional with the compilation database, so we can serialize the args in the compile_commands.json + // format and then join them in a one-liner string, so they're easy to read and/or copy } - fn normalize_execution_result_status( + fn _normalize_execution_result_status( + // TODO: pending to re-implement it &self, module_command_line: &SourceCommandLine, ) -> CommandExecutionResult { @@ -263,39 +200,7 @@ impl ZorkCache { } } - fn extend_collection_of_source_file_details( - &mut self, - model: &ZorkModel, - collection: &mut Vec, - target: &mut [SourceCommandLine], - compiler: CppCompiler, - ) -> bool { - let mut new_commands = false; - collection.extend(target.iter().map(|source_command_line| { - self.last_generated_commands - .entry(source_command_line.path()) - .or_insert_with(|| { - new_commands = true; - let mut arguments = Vec::with_capacity(source_command_line.args.len() + 1); - arguments.push(compiler.get_driver(&model.compiler).to_string()); - arguments.extend(source_command_line.args.iter().map(|e| e.value.to_string())); - arguments - }); - CommandDetail { - directory: source_command_line - .directory - .to_str() - .unwrap_or_default() - .to_string(), - file: source_command_line.filename.clone(), - execution_result: self.normalize_execution_result_status(source_command_line), - } - })); - - new_commands - } - - /// Method that returns the HashMap that holds the enviromental variables that must be passed + /// Method that returns the HashMap that holds the environmental variables that must be passed /// to the underlying shell pub fn get_process_env_args(&self) -> &EnvVars { match self.compiler { @@ -305,11 +210,33 @@ impl ZorkCache { } } + // TODO: + pub fn get_all_commands_iter(&self) -> impl Iterator + Debug + '_ { + let latest = self.generated_commands.last().unwrap(); + latest + .pre_tasks + .iter() + .chain(latest.interfaces.iter()) + .chain(latest.implementations.iter()) + .chain(latest.sources.iter()) + } + + pub fn count_total_generated_commands(&self) -> usize { + let latest_commands = self.generated_commands.last().unwrap(); + + latest_commands.interfaces.len() + + latest_commands.implementations.len() + + latest_commands.sources.len() + + latest_commands.pre_tasks.len() + + 1 // TODO: the linker one + } + /// Looks for the already precompiled `GCC` or `Clang` system headers, /// to avoid recompiling them on every process /// NOTE: This feature should be deprecated an therefore, removed from Zork++ when GCC and /// Clang fully implement the required procedures to build the C++ std library as a module fn track_system_modules<'a>( + // TODO move it to helpers program_data: &'a ZorkModel<'_>, ) -> impl Iterator + 'a { let root = if program_data.compiler.cpp_compiler == CppCompiler::GCC { @@ -368,7 +295,8 @@ pub struct CommandsDetails { pub struct CommandDetail { directory: String, file: String, - pub execution_result: CommandExecutionResult, + command_line: String, + execution_result: CommandExecutionResult, } impl CommandDetail { @@ -376,6 +304,11 @@ impl CommandDetail { pub fn file_path(&self) -> PathBuf { Path::new(&self.directory).join(&self.file) } + + #[inline] + pub fn execution_result(&self) -> CommandExecutionResult { + self.execution_result + } } #[derive(Deserialize, Serialize, Debug, Default, Clone)] @@ -390,6 +323,7 @@ pub type EnvVars = HashMap; #[derive(Deserialize, Serialize, Debug, Default, Clone)] pub struct CompilersMetadata { + // ALL of them must be optional, since only exists pub msvc: MsvcMetadata, pub clang: ClangMetadata, pub gcc: GccMetadata, @@ -430,15 +364,15 @@ pub struct GccMetadata { /// Helper procedures to process cache data for Microsoft's MSVC mod msvc { - use std::collections::HashMap; - use std::path::Path; - use color_eyre::eyre::{Context, OptionExt}; - use regex::Regex; - use crate::cache::ZorkCache; + use crate::cache::{msvc, ZorkCache}; use crate::project_model::sourceset::SourceFile; use crate::project_model::ZorkModel; use crate::utils; use crate::utils::constants; + use color_eyre::eyre::{Context, OptionExt}; + use regex::Regex; + use std::collections::HashMap; + use std::path::Path; /// If Windows is the current OS, and the compiler is MSVC, then we will try /// to locate the path of the `vcvars64.bat` script that will set a set of environmental @@ -447,7 +381,10 @@ mod msvc { /// After such effort, we will dump those env vars to a custom temporary file where every /// env var is registered there in a key-value format, so we can load it into the cache and /// run this process once per new cache created (cache action 1) - fn load_metadata(cache: &mut ZorkCache, program_data: &ZorkModel<'_>) -> color_eyre::Result<()> { + pub(crate) fn load_metadata( + cache: &mut ZorkCache, + program_data: &ZorkModel<'_>, + ) -> color_eyre::Result<()> { let msvc = &mut cache.compilers_metadata.msvc; if msvc.dev_commands_prompt.is_none() { @@ -457,12 +394,12 @@ mod msvc { Path::new(constants::MSVC_REGULAR_BASE_PATH), constants::MS_ENV_VARS_BAT, ) - .map(|walkdir_entry| { - walkdir_entry.path().to_string_lossy().replace( - constants::MSVC_REGULAR_BASE_PATH, - constants::MSVC_REGULAR_BASE_SCAPED_PATH, - ) - }); + .map(|walkdir_entry| { + walkdir_entry.path().to_string_lossy().replace( + constants::MSVC_REGULAR_BASE_PATH, + constants::MSVC_REGULAR_BASE_SCAPED_PATH, + ) + }); let output = std::process::Command::new(constants::WIN_CMD) .arg("/c") .arg(msvc.dev_commands_prompt.as_ref().ok_or_eyre("Zork++ wasn't unable to find the VS env vars")?) @@ -471,7 +408,7 @@ mod msvc { .output() .with_context(|| "Unable to load MSVC pre-requisites. Please, open an issue with the details on upstream")?; - msvc.env_vars = Self::load_env_vars_from_cmd_output(&output.stdout)?; + msvc.env_vars = msvc::load_env_vars_from_cmd_output(&output.stdout)?; // Cloning the useful ones for quick access at call site msvc.compiler_version = msvc.env_vars.get("VisualStudioVersion").cloned(); diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index cbeaf9e4..436b5aa4 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -9,74 +9,68 @@ use serde::{Deserialize, Serialize}; /// Wrapper type for represent and storing a command line argument #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Argument<'a> { - pub value: &'a str, +pub struct Argument(String); + +impl Argument { + pub fn value(&self) -> &String { + &self.0 + } } -impl<'a> From<&'a str> for Argument<'a> { - fn from(value: &'a str) -> Self { - Self { value } +impl From<&str> for Argument { + fn from(value: &str) -> Self { + Self(value.into()) } } -impl<'a> From for Argument<'a> { - fn from(value: String) -> Argument<'a> { - Self { - value: Box::leak(value.into_boxed_str()), - } +impl From for Argument { + fn from(value: String) -> Argument { + Self(value) } } -impl<'a> From<&'a Path> for Argument<'a> { - fn from(value: &'a Path) -> Self { +impl From<&Path> for Argument { + fn from(value: &Path) -> Self { Self::from(format!("{}", value.display())) } } -impl<'a> From for Argument<'a> { +impl From for Argument { fn from(value: PathBuf) -> Self { Self::from(format!("{}", value.display())) } } -impl<'a> From<&PathBuf> for Argument<'a> { +impl From<&PathBuf> for Argument { fn from(value: &PathBuf) -> Self { Self::from(format!("{}", value.display())) } } -impl<'a> Deref for Argument<'a> { - type Target = &'a str; - - fn deref(&self) -> &Self::Target { - &self.value - } -} - -impl<'a> Borrow for Argument<'a> { +impl Borrow for Argument { fn borrow(&self) -> &str { - self.value + &self.0 } } -impl<'a> AsRef for Argument<'a> { +impl AsRef for Argument { fn as_ref(&self) -> &OsStr { - OsStr::new(self.value) + OsStr::new(&self.0) } } -impl<'a> core::fmt::Display for Argument<'a> { +impl core::fmt::Display for Argument { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.value) + write!(f, "{}", self.0) } } /// Strong type for represent a linear collection of [`Argument`] -#[derive(Debug, Default, Clone)] -pub struct Arguments<'a>(Vec>); -impl<'a> Arguments<'a> { +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Arguments(Vec); +impl Arguments { /// Wraps an existing [`std::vec::Vec`] of [`Argument`] - pub fn from_vec(vec: Vec>) -> Self { + pub fn from_vec(vec: Vec) -> Self { Self(vec) } @@ -86,48 +80,48 @@ impl<'a> Arguments<'a> { } /// Creates and stores a new [`Argument`] to the end of this collection + /// from any type *T* that can be coerced into an [`Argument`] type pub fn create_and_push(&mut self, val: T) where - T: Into>, + T: Into, { self.0.push(val.into()) } /// Appends a new [`Argument`] to the end of this collection - pub fn push(&mut self, arg: Argument<'a>) { + pub fn push(&mut self, arg: Argument) { self.0.push(arg) } // TODO: aren't this one and the one above redundant? Wouldn't be better to unify both // interfaces in only one method call? With a better name, btw? Like or - /// Given an optional, adds the wrapper inner value if there's some element, - /// otherwise leaves - pub fn push_opt(&mut self, arg: Option>) { + /// Given an optional, adds the inner value if there's Some(<[Argument]>) + pub fn push_opt(&mut self, arg: Option) { if let Some(val) = arg { self.0.push(val) } } - /// Extends the underlying collection from a Iterator of [`Argument`] - pub fn extend(&mut self, iter: impl IntoIterator>) { + /// Extends the underlying collection from an Iterator of [`Argument`] + pub fn extend(&mut self, iter: impl IntoIterator) { self.0.extend(iter); } /// Extends the underlying collection given a slice of [`Argument`] - pub fn extend_from_slice(&mut self, slice: &'a [Argument<'a>]) { + pub fn extend_from_slice(&mut self, slice: &[Argument]) { self.0.extend_from_slice(slice); } } -impl<'a> Deref for Arguments<'a> { - type Target = [Argument<'a>]; +impl Deref for Arguments { + type Target = [Argument]; fn deref(&self) -> &Self::Target { &self.0 } } -impl<'a> IntoIterator for Arguments<'a> { - type Item = Argument<'a>; +impl IntoIterator for Arguments { + type Item = Argument; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { @@ -135,7 +129,7 @@ impl<'a> IntoIterator for Arguments<'a> { } } -/// Isolated module to storing custom procedures to easy create and add new command line arguments +/// Isolated module to storing custom procedures to easily create and add new command line arguments /// or flags specific to Clang, that otherwise, will be bloating the main procedures with a lot /// of cognitive complexity pub mod clang_args { @@ -150,7 +144,7 @@ pub mod clang_args { // The Windows variant is a Zork++ feature to allow the users to write `import std;` // under -std=c++20 with clang linking against GCC with // some MinGW installation or similar - pub(crate) fn implicit_module_maps<'a>(out_dir: &Path) -> Argument<'a> { + pub(crate) fn implicit_module_maps(out_dir: &Path) -> Argument { if std::env::consts::OS.eq("windows") { Argument::from(format!( "-fmodule-map-file={}", @@ -165,7 +159,7 @@ pub mod clang_args { } } - pub(crate) fn add_prebuilt_module_path(compiler: CppCompiler, out_dir: &Path) -> Argument<'_> { + pub(crate) fn add_prebuilt_module_path(compiler: CppCompiler, out_dir: &Path) -> Argument { Argument::from(format!( "-fprebuilt-module-path={}", out_dir @@ -180,7 +174,7 @@ pub mod clang_args { dependencies: &[&str], compiler: CppCompiler, out_dir: &Path, - arguments: &mut Arguments<'_>, + arguments: &mut Arguments, ) { dependencies.iter().for_each(|ifc_dep| { arguments.push(Argument::from(format!( @@ -207,11 +201,11 @@ pub mod msvc_args { use super::Arguments; - pub(crate) fn generate_std_cmd_args<'a>( - model: &'a ZorkModel<'_>, + pub(crate) fn generate_std_cmd_args( + model: &ZorkModel<'_>, cache: &ZorkCache, stdlib_mode: StdLibMode, - ) -> SourceCommandLine<'a> { + ) -> SourceCommandLine { let mut arguments = Arguments::default(); let msvc = &cache.compilers_metadata.msvc; diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index 28fbda7f..f5e10db1 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -26,7 +26,7 @@ use super::arguments::Argument; pub fn run_generated_commands( program_data: &ZorkModel<'_>, - mut commands: Commands<'_>, + mut commands: Commands, // TODO: &mut, and then directly store them? cache: &mut ZorkCache, test_mode: bool, ) -> Result { @@ -63,11 +63,11 @@ pub fn run_generated_commands( } } - if !commands.main.args.is_empty() { - log::debug!("Executing the main command line..."); + if !commands.linker.args.is_empty() { + log::debug!("Executing the main command line..."); // TODO: this refers to the linker - let r = execute_command(compiler, program_data, &commands.main.args, cache); - commands.main.execution_result = CommandExecutionResult::from(&r); + let r = execute_command(compiler, program_data, &commands.linker.args, cache); + commands.linker.execution_result = CommandExecutionResult::from(&r); if let Err(e) = r { cache::save(program_data, cache, commands, test_mode)?; @@ -118,7 +118,7 @@ pub fn autorun_generated_binary( fn execute_command( compiler: CppCompiler, model: &ZorkModel, - arguments: &[Argument<'_>], + arguments: &[Argument], cache: &ZorkCache, ) -> Result { log::trace!( @@ -138,22 +138,25 @@ fn execute_command( .with_context(|| format!("[{compiler}] - Command {:?} failed!", arguments.join(" "))) } -/// The pieces and details for the generated command line for +/// The pieces and details for the generated command line /// for some translation unit -#[derive(Debug)] -pub struct SourceCommandLine<'a> { +/// +/// * args* : member that holds all the cmd arguments that will be passed to the compiler driver +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SourceCommandLine { pub directory: PathBuf, pub filename: String, - pub args: Arguments<'a>, + pub args: Arguments, pub processed: bool, pub execution_result: CommandExecutionResult, // TODO an enum with the Kind OF TU that is generating this scl? } -impl<'a> SourceCommandLine<'a> { +impl SourceCommandLine { pub fn from_translation_unit( + // TODO init it as a args holder, but doesn't have the status yet tu: impl TranslationUnit, - args: Arguments<'a>, // TODO: maybe this should be an option? Cached arguments are passed + args: Arguments, // TODO: maybe this should be an option? Cached arguments are passed // here as default. So probably, even better than having an optional, // we must replicate this to have a separate entity like // CachedSourceCommandLine, and them just call them over any kind of @@ -176,54 +179,66 @@ impl<'a> SourceCommandLine<'a> { } } -#[derive(Debug)] -pub struct ExecutableCommandLine<'a> { - pub main: &'a Path, - pub sources_paths: Vec, - pub args: Vec>, +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct LinkerCommandLine { + // pub main: &'a Path, // TODO: can't this disappear? At the end of the day, is just another obj file + pub built_files: Vec, + pub args: Vec, pub execution_result: CommandExecutionResult, } -impl<'a> Default for ExecutableCommandLine<'a> { - fn default() -> Self { - Self { - main: Path::new("."), - sources_paths: Vec::with_capacity(0), - args: Vec::with_capacity(0), - execution_result: Default::default(), - } +impl LinkerCommandLine { + /// Saves the path at which a compilation product of any translation unit will be placed, + /// in order to add it to the files that will be linked to generate the final product + /// in the two-phase compilation model + pub fn add_buildable_at(&mut self, path: &Path) { + self.built_files.push(path.to_path_buf()); + } + + /// Owned version of TODO link + pub fn add_owned_buildable_at(&mut self, path: PathBuf) { + self.built_files.push(path); } } /// Holds the generated command line arguments for a concrete compiler -#[derive(Debug)] -pub struct Commands<'a> { +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Commands { pub compiler: CppCompiler, - pub pre_tasks: Vec>, - pub system_modules: HashMap>, - pub interfaces: Vec>, - pub implementations: Vec>, - pub sources: Vec>, - pub main: ExecutableCommandLine<'a>, - pub generated_files_paths: Arguments<'a>, + pub pre_tasks: Vec, // TODO: since there's no really pre-tasks (only build the std_lib), create named entries for std and std.compat + pub system_modules: HashMap, + pub interfaces: Vec, + pub implementations: Vec, + pub sources: Vec, + pub linker: LinkerCommandLine, } -impl<'a> Commands<'a> { - pub fn new(compiler: &'a CppCompiler) -> Self { +impl Commands { + pub fn new(compiler: CppCompiler) -> Self { Self { - compiler: *compiler, + // TODO: try to see if its possible to move around the code and have a From, avoiding default initialization, + // since this will always cause reallocations, and from may be able to allocate at the exact required capacity + // of every collection + compiler, pre_tasks: Vec::with_capacity(0), system_modules: HashMap::with_capacity(0), interfaces: Vec::with_capacity(0), implementations: Vec::with_capacity(0), sources: Vec::with_capacity(0), - main: ExecutableCommandLine::default(), - generated_files_paths: Arguments::default(), + linker: LinkerCommandLine::default(), } } + + pub fn add_linker_file_path(&mut self, path: &Path) { + self.linker.add_buildable_at(path); + } + + pub fn add_linker_file_path_owned(&mut self, path: PathBuf) { + self.linker.add_owned_buildable_at(path); + } } -impl<'a> core::fmt::Display for Commands<'a> { +impl core::fmt::Display for Commands { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, @@ -237,13 +252,13 @@ impl<'a> core::fmt::Display for Commands<'a> { } /// Convenient function to avoid code replication -fn collect_source_command_line<'a>( - iter: Iter<'a, SourceCommandLine<'a>>, -) -> impl Iterator + Debug + 'a { +fn collect_source_command_line( + iter: Iter<'_, SourceCommandLine>, // TODO: review this, for see if it's possible to consume the value and not cloning it +) -> impl Iterator + Debug + '_ { iter.map(|vec| { vec.args .iter() - .map(|e| e.value) + .map(|arg| arg.value().clone()) .collect::>() .join(" "); }) diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 99796267..5c39dbcf 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -30,9 +30,9 @@ pub fn build_project<'a>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache, tests: bool, -) -> Result> { +) -> Result { // A registry of the generated command lines - let mut commands = Commands::new(&model.compiler.cpp_compiler); + let mut commands = Commands::new(model.compiler.cpp_compiler); // Pre-tasks if model.compiler.cpp_compiler == CppCompiler::GCC && !model.modules.sys_modules.is_empty() { @@ -53,11 +53,7 @@ pub fn build_project<'a>( /// Builds the C++ standard library as a pre-step acording to the specification /// of each compiler vendor -fn build_modular_stdlib<'a>( - model: &'a ZorkModel<'_>, - cache: &mut ZorkCache, - commands: &mut Commands<'a>, -) { +fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: &mut Commands) { let compiler = model.compiler.cpp_compiler; // TODO: remaining ones: Clang, GCC @@ -70,6 +66,7 @@ fn build_modular_stdlib<'a>( ); msvc_args::generate_std_cmd_args(model, cache, StdLibMode::Cpp) } else { + // TODO: p.ej: existe y además tiene status cached? modificar por &mut let source_command_line = SourceCommandLine { directory: built_stdlib_path.file_stem().unwrap().into(), filename: built_stdlib_path @@ -113,10 +110,10 @@ fn build_modular_stdlib<'a>( /// Triggers the build process for compile the source files declared for the project /// If this flow is enabled by the Cli arg `Tests`, then the executable will be generated /// for the files and properties declared for the tests section in the configuration file -fn build_executable<'a>( - model: &'a ZorkModel<'_>, +fn build_executable( + model: &ZorkModel<'_>, cache: &ZorkCache, - commands: &'_ mut Commands<'a>, + commands: &'_ mut Commands, tests: bool, ) -> Result<()> { // TODO: Check if the command line is the same as the previous? If there's no new sources? @@ -129,10 +126,10 @@ fn build_executable<'a>( } } -fn build_sources<'a>( - model: &'a ZorkModel<'_>, +fn build_sources( + model: &ZorkModel<'_>, cache: &ZorkCache, - commands: &'_ mut Commands<'a>, + commands: &'_ mut Commands, tests: bool, ) -> Result<()> { log::info!("Building the source files..."); @@ -151,9 +148,9 @@ fn build_sources<'a>( log::trace!("Source file: {:?} was not modified since the last iteration. No need to rebuilt it again.", &src.file()); commands.sources.push(command_line); - commands.generated_files_paths.push(Argument::from(helpers::generate_obj_file( + commands.add_linker_file_path_owned(helpers::generate_obj_file_path( model.compiler.cpp_compiler, &model.build.output_dir, src - ).to_string())) + )) }); Ok(()) @@ -164,11 +161,7 @@ fn build_sources<'a>( /// This function acts like a operation result processor, by running instances /// and parsing the obtained result, handling the flux according to the /// compiler responses> -fn build_modules<'a>( - model: &'a ZorkModel, - cache: &ZorkCache, - commands: &mut Commands<'a>, -) -> Result<()> { +fn build_modules(model: &ZorkModel, cache: &ZorkCache, commands: &mut Commands) -> Result<()> { log::info!("Building the module interfaces and partitions..."); build_module_interfaces(model, cache, &model.modules.interfaces, commands); @@ -184,7 +177,7 @@ fn build_module_interfaces<'a>( model: &'a ZorkModel<'_>, cache: &ZorkCache, interfaces: &'a [ModuleInterfaceModel], - commands: &mut Commands<'a>, + commands: &mut Commands, ) { interfaces.iter().for_each(|module_interface| { if !flag_source_file_without_changes(&model.compiler.cpp_compiler, cache, &module_interface.file()) { @@ -196,9 +189,9 @@ fn build_module_interfaces<'a>( log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &module_interface.file()); commands.interfaces.push(command_line); - commands.generated_files_paths.push(Argument::from(helpers::generate_prebuilt_miu( + commands.linker.add_owned_buildable_at(helpers::generate_prebuilt_miu( model.compiler.cpp_compiler, &model.build.output_dir, module_interface - ))) + )) } }); } @@ -209,7 +202,7 @@ fn build_module_implementations<'a>( model: &'a ZorkModel, cache: &ZorkCache, impls: &'a [ModuleImplementationModel], - commands: &mut Commands<'a>, + commands: &mut Commands, ) { impls.iter().for_each(|module_impl| { if !flag_source_file_without_changes(&model.compiler.cpp_compiler, cache, &module_impl.file()) { @@ -223,9 +216,9 @@ fn build_module_implementations<'a>( commands.implementations.push(command_line); // TODO:: There's other todo where we // explain why we should change this code // (and the other similar ones) - commands.generated_files_paths.push(Argument::from(helpers::generate_impl_obj_file( + commands.linker.add_owned_buildable_at(helpers::generate_impl_obj_file( model.compiler.cpp_compiler, &model.build.output_dir, module_impl - ))) + )) } }); } @@ -234,7 +227,7 @@ fn build_module_implementations<'a>( pub fn generate_main_command_line_args<'a>( model: &'a ZorkModel, cache: &ZorkCache, - commands: &mut Commands<'a>, + commands: &mut Commands, target: &'a impl ExecutableTarget<'a>, ) -> Result<()> { log::info!("Generating the main command line..."); @@ -312,10 +305,11 @@ pub fn generate_main_command_line_args<'a>( )); } }; - arguments.extend(commands.generated_files_paths.clone()); - commands.main.args.extend(arguments); - commands.main.sources_paths = target + arguments.extend(commands.linker.built_files.iter().map(Argument::from)); // TODO can't we avoid this, and just add the pathbufs? + + commands.linker.args.extend(arguments); + commands.linker.built_files = target .sourceset() .sources .iter() @@ -335,7 +329,7 @@ mod sources { use crate::{ bounds::{ExecutableTarget, TranslationUnit}, cli::output::{ - arguments::{clang_args, Argument}, + arguments::clang_args, commands::{CommandExecutionResult, Commands, SourceCommandLine}, }, project_model::{ @@ -348,7 +342,7 @@ mod sources { /// Generates the command line arguments for non-module source files pub fn generate_sources_arguments<'a>( model: &'a ZorkModel, - commands: &mut Commands<'a>, + commands: &mut Commands, cache: &ZorkCache, target: &'a impl ExecutableTarget<'a>, source: &'a SourceFile, @@ -399,13 +393,13 @@ mod sources { } }; - let obj_file = helpers::generate_obj_file(compiler, out_dir, source); + let obj_file = helpers::generate_obj_file_path(compiler, out_dir, source); let fo = if compiler.eq(&CppCompiler::MSVC) { "/Fo" } else { "" }; - arguments.create_and_push(format!("{fo}{obj_file}")); + arguments.create_and_push(format!("{fo}{}", obj_file.display())); arguments.create_and_push(source.file()); let command_line = SourceCommandLine::from_translation_unit( @@ -415,9 +409,7 @@ mod sources { CommandExecutionResult::default(), ); commands.sources.push(command_line); - commands - .generated_files_paths - .push(Argument::from(obj_file.to_string())) + commands.add_linker_file_path_owned(obj_file) } /// Generates the expected arguments for precompile the BMIs depending on self @@ -425,7 +417,7 @@ mod sources { model: &'a ZorkModel, cache: &ZorkCache, interface: &'a ModuleInterfaceModel, - commands: &mut Commands<'a>, + commands: &mut Commands, ) { let compiler = model.compiler.cpp_compiler; let out_dir = model.build.output_dir.as_ref(); @@ -457,10 +449,9 @@ mod sources { // The resultant BMI as a .pcm file arguments.create_and_push("-o"); // The output file - let miu_file_path = - Argument::from(helpers::generate_prebuilt_miu(compiler, out_dir, interface)); - commands.generated_files_paths.push(miu_file_path.clone()); - arguments.push(miu_file_path); + let obj_file = helpers::generate_prebuilt_miu(compiler, out_dir, interface); + arguments.create_and_push(&obj_file); + commands.add_linker_file_path_owned(obj_file); // The input file arguments.create_and_push(interface.file()); } @@ -489,10 +480,9 @@ mod sources { arguments.create_and_push(implicit_lookup_mius_path); // The output .obj file - let obj_file = - Argument::from(helpers::generate_prebuilt_miu(compiler, out_dir, interface)); - commands.generated_files_paths.push(obj_file.clone()); - arguments.create_and_push(format!("/Fo{obj_file}")); + let obj_file = helpers::generate_prebuilt_miu(compiler, out_dir, interface); + arguments.create_and_push(format!("/Fo{}", obj_file.display())); + commands.add_linker_file_path_owned(obj_file); if let Some(partition) = &interface.partition { if partition.is_internal_partition { @@ -516,10 +506,9 @@ mod sources { arguments.create_and_push(interface.file()); // The output file arguments.create_and_push("-o"); - let miu_file_path = - Argument::from(helpers::generate_prebuilt_miu(compiler, out_dir, interface)); - commands.generated_files_paths.push(miu_file_path.clone()); - arguments.push(miu_file_path); + let obj_file = helpers::generate_prebuilt_miu(compiler, out_dir, interface); + arguments.create_and_push(&obj_file); + commands.add_linker_file_path_owned(obj_file); } } @@ -537,7 +526,7 @@ mod sources { model: &'a ZorkModel, cache: &ZorkCache, implementation: &'a ModuleImplementationModel, - commands: &mut Commands<'a>, + commands: &mut Commands, ) { let compiler = model.compiler.cpp_compiler; let out_dir = model.build.output_dir.as_ref(); @@ -556,13 +545,14 @@ mod sources { // The resultant object file arguments.create_and_push("-o"); - let obj_file_path = Argument::from(helpers::generate_impl_obj_file( + let obj_file_path = helpers::generate_impl_obj_file( + // TODO review this ones, since they module impl are just raw cpp sources compiler, out_dir, implementation, - )); - commands.generated_files_paths.push(obj_file_path.clone()); - arguments.push(obj_file_path); + ); + commands.add_linker_file_path(&obj_file_path); + arguments.create_and_push(obj_file_path); clang_args::add_direct_module_interfaces_dependencies( &implementation.dependencies, @@ -596,16 +586,14 @@ mod sources { // The input file arguments.create_and_push(implementation.file()); // The output .obj file - let obj_file_path = out_dir + let obj_file_path = out_dir // TODO use the helper .join(compiler.as_ref()) .join("modules") .join("implementations") .join(implementation.file_stem()) .with_extension(compiler.get_obj_file_extension()); - commands - .generated_files_paths - .push(Argument::from(obj_file_path.clone())); + commands.add_linker_file_path(&obj_file_path); arguments.create_and_push(format!("/Fo{}", obj_file_path.display())); } CppCompiler::GCC => { @@ -615,13 +603,10 @@ mod sources { arguments.create_and_push(implementation.file()); // The output file arguments.create_and_push("-o"); - let obj_file_path = Argument::from(helpers::generate_impl_obj_file( - compiler, - out_dir, - implementation, - )); - commands.generated_files_paths.push(obj_file_path.clone()); - arguments.push(obj_file_path); + let obj_file_path = + helpers::generate_impl_obj_file(compiler, out_dir, implementation); + commands.add_linker_file_path(&obj_file_path); + arguments.create_and_push(obj_file_path); } } @@ -640,7 +625,6 @@ mod sources { /// generate. mod helpers { use chrono::{DateTime, Utc}; - use std::fmt::Display; use super::*; use crate::project_model::sourceset::SourceFile; @@ -716,11 +700,7 @@ mod helpers { /// /// This is for `GCC` and `Clang` /// TODO: With the inclusion of std named modules, want we to support this anymore? - pub(crate) fn build_sys_modules<'a>( - model: &'a ZorkModel, - commands: &mut Commands<'a>, - cache: &ZorkCache, - ) { + pub(crate) fn build_sys_modules(model: &ZorkModel, commands: &mut Commands, cache: &ZorkCache) { if !cache.compilers_metadata.system_modules.is_empty() { // TODO BUG - this is not correct. // If user later adds a new module, it won't be processed @@ -784,7 +764,7 @@ mod helpers { for collection_args in sys_modules { commands.system_modules.insert( // [3] is for the 4th flag pushed to v - collection_args[3].value.to_string(), + collection_args[3].value().to_string(), Arguments::from_vec(collection_args), ); } @@ -809,13 +789,14 @@ mod helpers { return false; } // Check first if the file is already on the cache, and if it's last iteration was successful - if let Some(cached_file) = cache.is_file_cached(file) { - if cached_file.execution_result != CommandExecutionResult::Success - && cached_file.execution_result != CommandExecutionResult::Cached + if let Some(cached_compiled_file) = cache.is_file_cached(file) { + let execution_result = cached_compiled_file.execution_result(); + if execution_result != CommandExecutionResult::Success + && execution_result != CommandExecutionResult::Cached { log::trace!( "File {file:?} with status: {:?}. Marked to reprocess", - cached_file.execution_result + execution_result ); return false; }; @@ -841,19 +822,15 @@ mod helpers { } } - pub(crate) fn generate_obj_file( + pub(crate) fn generate_obj_file_path( compiler: CppCompiler, out_dir: &Path, source: &SourceFile, - ) -> impl Display { - format!( - "{}", - out_dir - .join(compiler.as_ref()) - .join("sources") - .join(source.file_stem()) - .with_extension(compiler.get_obj_file_extension()) - .display() - ) + ) -> PathBuf { + out_dir + .join(compiler.as_ref()) + .join("sources") + .join(source.file_stem()) + .with_extension(compiler.get_obj_file_extension()) } } diff --git a/zork++/src/lib/project_model/compiler.rs b/zork++/src/lib/project_model/compiler.rs index 9fee6de6..23826a20 100644 --- a/zork++/src/lib/project_model/compiler.rs +++ b/zork++/src/lib/project_model/compiler.rs @@ -10,7 +10,7 @@ pub struct CompilerModel<'a> { pub driver_path: &'a str, pub cpp_standard: LanguageLevel, pub std_lib: Option, - pub extra_args: Vec>, + pub extra_args: Vec, } impl<'a> CompilerModel<'a> { @@ -31,7 +31,7 @@ impl<'a> CompilerModel<'a> { } impl<'a> ExtraArgs<'a> for CompilerModel<'a> { - fn extra_args(&'a self) -> &'a [Argument<'a>] { + fn extra_args(&'a self) -> &'a [Argument] { &self.extra_args } } diff --git a/zork++/src/lib/project_model/executable.rs b/zork++/src/lib/project_model/executable.rs index 9d2d4a9e..762bf152 100644 --- a/zork++/src/lib/project_model/executable.rs +++ b/zork++/src/lib/project_model/executable.rs @@ -9,11 +9,11 @@ use super::sourceset::SourceSet; pub struct ExecutableModel<'a> { pub executable_name: &'a str, pub sourceset: SourceSet, - pub extra_args: Vec>, + pub extra_args: Vec, } impl<'a> ExtraArgs<'a> for ExecutableModel<'a> { - fn extra_args(&'a self) -> &'a [Argument<'a>] { + fn extra_args(&'a self) -> &'a [Argument] { &self.extra_args } } diff --git a/zork++/src/lib/project_model/mod.rs b/zork++/src/lib/project_model/mod.rs index 467e9046..34779303 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -20,5 +20,5 @@ pub struct ZorkModel<'a> { pub build: BuildModel, pub executable: ExecutableModel<'a>, pub modules: ModulesModel<'a>, - pub tests: TestsModel<'a>, + pub tests: TestsModel, } diff --git a/zork++/src/lib/project_model/modules.rs b/zork++/src/lib/project_model/modules.rs index 2b2147f5..2466b055 100644 --- a/zork++/src/lib/project_model/modules.rs +++ b/zork++/src/lib/project_model/modules.rs @@ -12,11 +12,11 @@ pub struct ModulesModel<'a> { pub base_impls_dir: &'a Path, pub implementations: Vec>, pub sys_modules: Vec<&'a str>, - pub extra_args: Vec>, + pub extra_args: Vec, } impl<'a> ExtraArgs<'a> for ModulesModel<'a> { - fn extra_args(&'a self) -> &'a [Argument<'a>] { + fn extra_args(&'a self) -> &'a [Argument] { &self.extra_args } } diff --git a/zork++/src/lib/project_model/sourceset.rs b/zork++/src/lib/project_model/sourceset.rs index d0945697..dfb55000 100644 --- a/zork++/src/lib/project_model/sourceset.rs +++ b/zork++/src/lib/project_model/sourceset.rs @@ -119,7 +119,7 @@ pub struct SourceSet { } impl SourceSet { - pub fn as_args_to(&self, dst: &mut Vec>) -> Result<()> { + pub fn as_args_to(&self, dst: &mut Vec) -> Result<()> { let args = self.sources.iter().map(|sf| sf.file()).map(Argument::from); dst.extend(args); diff --git a/zork++/src/lib/project_model/tests.rs b/zork++/src/lib/project_model/tests.rs index 336ca341..e8dd7afb 100644 --- a/zork++/src/lib/project_model/tests.rs +++ b/zork++/src/lib/project_model/tests.rs @@ -6,19 +6,19 @@ use crate::{ use super::sourceset::SourceSet; #[derive(Debug, PartialEq, Eq)] -pub struct TestsModel<'a> { +pub struct TestsModel { pub test_executable_name: String, pub sourceset: SourceSet, - pub extra_args: Vec>, + pub extra_args: Vec, } -impl<'a> ExtraArgs<'a> for TestsModel<'a> { - fn extra_args(&'a self) -> &'a [Argument<'a>] { +impl<'a> ExtraArgs<'a> for TestsModel { + fn extra_args(&'a self) -> &'a [Argument] { &self.extra_args } } -impl<'a> ExecutableTarget<'a> for TestsModel<'a> { +impl<'a> ExecutableTarget<'a> for TestsModel { fn name(&'a self) -> &'a str { &self.test_executable_name } diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 8c36d52f..b628dac4 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -315,7 +315,7 @@ fn assemble_tests_model<'a>( project_name: &'a str, config: &'a Option, project_root: &Path, -) -> TestsModel<'a> { +) -> TestsModel { let config = config.as_ref(); let test_executable_name = config.and_then(|exe| exe.test_executable_name).map_or_else( From 3c41152f9f2ff8f7df368ee0429549b934587f87 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 6 Jun 2024 12:50:13 +0200 Subject: [PATCH 03/73] feat(wip): reworking how the cache store the generated commands, and avoids --- zork++/src/lib/cache/compile_commands.rs | 19 ++-- zork++/src/lib/cache/mod.rs | 87 ++++++++++++----- zork++/src/lib/cli/output/arguments.rs | 2 +- zork++/src/lib/cli/output/commands.rs | 34 +++++-- zork++/src/lib/compiler/mod.rs | 118 ++++++++++++++++++----- zork++/src/lib/lib.rs | 3 +- 6 files changed, 196 insertions(+), 67 deletions(-) diff --git a/zork++/src/lib/cache/compile_commands.rs b/zork++/src/lib/cache/compile_commands.rs index 273cd697..e2fd881e 100644 --- a/zork++/src/lib/cache/compile_commands.rs +++ b/zork++/src/lib/cache/compile_commands.rs @@ -8,18 +8,21 @@ use serde::Serialize; use std::fs::File; use std::path::{Path, PathBuf}; +pub type CompileCommands = Vec; + /// Generates the `compile_commands.json` file, that acts as a compilation database /// for some static analysis external tools, like `clang-tidy`, and populates it with /// the generated commands for the translation units -pub(crate) fn map_generated_commands_to_compilation_db(cache: &ZorkCache) -> Result<()> { +pub(crate) fn map_generated_commands_to_compilation_db(cache: &ZorkCache) -> Result { log::trace!("Generating the compilation database..."); + let generated_commands = cache.get_all_commands_iter(); - let mut compilation_db_entries: Vec = + let mut compilation_db_entries: Vec = Vec::with_capacity(cache.count_total_generated_commands()); - // compilation_db_entries.push(latest_commands.linker) + // TODO: compilation_db_entries.push(latest_commands.linker) for command in generated_commands { - compilation_db_entries.push(CompileCommands::from(command)); + compilation_db_entries.push(CompileCommand::from(command)); } let compile_commands_path = Path::new(COMPILATION_DATABASE); @@ -28,19 +31,21 @@ pub(crate) fn map_generated_commands_to_compilation_db(cache: &ZorkCache) -> Res .with_context(|| "Error creating the compilation database")?; } utils::fs::serialize_object_to_file(Path::new(compile_commands_path), &compilation_db_entries) - .with_context(move || "Error saving the compilation database") + .with_context(move || "Error saving the compilation database")?; + + Ok(compilation_db_entries) } /// Data model for serialize the data that will be outputted /// to the `compile_commands.json` compilation database file #[derive(Serialize, Debug, Default, Clone)] -pub struct CompileCommands { +pub struct CompileCommand { pub directory: PathBuf, pub file: String, pub arguments: Arguments, } -impl From<&SourceCommandLine> for CompileCommands { +impl From<&SourceCommandLine> for CompileCommand { fn from(value: &SourceCommandLine) -> Self { let value = value.clone(); Self { diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index c8400660..4fb60b5c 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -27,8 +27,12 @@ use crate::{ }; use serde::{Deserialize, Serialize}; use walkdir::WalkDir; +use crate::bounds::TranslationUnit; +use crate::cache::compile_commands::CompileCommands; +use crate::project_model::modules::ModuleInterfaceModel; -/// Standalone utility for retrieve the Zork++ cache file +/// Standalone utility for load from the file system the Zork++ cache file +/// for the target [`CppCompiler`] pub fn load(program_data: &ZorkModel<'_>, cli_args: &CliArgs) -> Result { let compiler = program_data.compiler.cpp_compiler; let cache_path = &program_data @@ -88,10 +92,18 @@ pub struct ZorkCache { pub compiler: CppCompiler, pub last_program_execution: DateTime, pub compilers_metadata: CompilersMetadata, - pub generated_commands: Vec, + pub generated_commands: Commands, } impl ZorkCache { + pub fn last_program_execution(&self) -> &DateTime { + &self.last_program_execution + } + pub fn get_cached_module_ifc_cmd(&self, module_interface_model: &ModuleInterfaceModel) -> Option<&SourceCommandLine>{ + self.generated_commands.interfaces.iter().find(|mi| + module_interface_model.file() == (*mi).path() + ) + } /// Returns a [`Option`] of [`CommandDetails`] if the file is persisted already in the cache pub fn is_file_cached(&self, _path: impl AsRef) -> Option<&CommandDetail> { // let last_iteration_details = self.generated_commands.last(); @@ -131,10 +143,16 @@ impl ZorkCache { commands: Commands, test_mode: bool, ) -> Result<()> { - if self.save_generated_commands(commands, program_data, test_mode) - && program_data.project.compilation_db - { - compile_commands::map_generated_commands_to_compilation_db(self)?; + // if self.save_generated_commands(commands, program_data, test_mode) + // && program_data.project.compilation_db + // { + // compile_commands::map_generated_commands_to_compilation_db(self)?; + // } + // + if let Some(_new_commands) = self.save_generated_commands(commands, program_data, test_mode) { + if program_data.project.compilation_db { // TODO:: pass the new commands + compile_commands::map_generated_commands_to_compilation_db(self)?; + } } if !(program_data.compiler.cpp_compiler == CppCompiler::MSVC) { @@ -151,38 +169,54 @@ impl ZorkCache { /// Stores the generated commands for the process in the Cache. /// ### Return: - /// a boolean indicating whether there's new generated commands (non cached), so + /// a [`Option `] indicating whether there's new generated commands (non cached), so /// the compile commands must be regenerated fn save_generated_commands( &mut self, commands: Commands, _model: &ZorkModel, _test_mode: bool, // TODO: tests linker cmd? - ) -> bool { + ) -> Option { log::debug!("Storing in the cache the last generated command lines..."); self.compiler = commands.compiler; - let _process_no = if !self.generated_commands.is_empty() { - // TODO: do we now need this one? - // self.generated_commands.last().unwrap().cached_process_num + 1 - 0 - } else { - 1 - }; + // let _process_no = if !self.generated_commands.is_empty() { + // // TODO: do we now need this one? + // // self.generated_commands.last().unwrap().cached_process_num + 1 + // 0 + // } else { + // 1 + // }; + + // Generating the compilation database if enabled, and some file has been added, modified + // or comes from a previous failure status + + // TODO: oh, fk, I get it finally. We should only regenerate the compilation database if + // the generated command line has changed! (which is highly unlikely) + // TODO: Create a wrapper enumerated over the Vec, so that we can store in the + // array full cached process, and have a variant that only points to the initial file // TODO missing the one that determines if there's a new compilation database that must be generated // something like and iter that counts if at least one has been modified ?? // let at_least_one_changed = commands. - self.generated_commands.push(commands); + self.generated_commands = commands; self.get_all_commands_iter()// TODO: Review the conditions and ensure that are the ones that we're looking for - .any(|cmd| cmd.processed || cmd.execution_result.eq(&CommandExecutionResult::Success)) + .any(|cmd| cmd.need_to_build || cmd.execution_result.eq(&CommandExecutionResult::Success)); // INSTEAD OF THIS, we just can return an Optional with the compilation database, so we can serialize the args in the compile_commands.json // format and then join them in a one-liner string, so they're easy to read and/or copy + None } fn _normalize_execution_result_status( // TODO: pending to re-implement it + // ALe, don't read again this. We just need to fix the implementation when the commands + // are generated, or even better, bring them from the cache + // So maybe, we can start by fix the thing on early stages + // discard cache if the target zork cfg file has been modified would be awesome + // then, we can have all of them paired in a hashmap with a unique autoincremental + // generated, and split by interfaces and so on and so forth, and read the commands + // from the cache &self, module_command_line: &SourceCommandLine, ) -> CommandExecutionResult { @@ -210,30 +244,31 @@ impl ZorkCache { } } - // TODO: + // TODO: read_only_iterator (better name) and docs pls pub fn get_all_commands_iter(&self) -> impl Iterator + Debug + '_ { - let latest = self.generated_commands.last().unwrap(); - latest + let generated_commands = &self.generated_commands; + + generated_commands .pre_tasks .iter() - .chain(latest.interfaces.iter()) - .chain(latest.implementations.iter()) - .chain(latest.sources.iter()) + .chain(generated_commands.interfaces.iter()) + .chain(generated_commands.implementations.iter()) + .chain(generated_commands.sources.iter()) } pub fn count_total_generated_commands(&self) -> usize { - let latest_commands = self.generated_commands.last().unwrap(); + let latest_commands = &self.generated_commands; latest_commands.interfaces.len() + latest_commands.implementations.len() + latest_commands.sources.len() + latest_commands.pre_tasks.len() - + 1 // TODO: the linker one + + 1 // TODO: the linker one? Does it supports it clangd? } /// Looks for the already precompiled `GCC` or `Clang` system headers, /// to avoid recompiling them on every process - /// NOTE: This feature should be deprecated an therefore, removed from Zork++ when GCC and + /// NOTE: This feature should be deprecated and therefore, removed from Zork++ when GCC and /// Clang fully implement the required procedures to build the C++ std library as a module fn track_system_modules<'a>( // TODO move it to helpers diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index 436b5aa4..a8b6981c 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -201,7 +201,7 @@ pub mod msvc_args { use super::Arguments; - pub(crate) fn generate_std_cmd_args( + pub(crate) fn generate_std_cmd( model: &ZorkModel<'_>, cache: &ZorkCache, stdlib_mode: StdLibMode, diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index f5e10db1..f3aa3f84 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -45,17 +45,17 @@ pub fn run_generated_commands( .chain(commands.implementations.iter_mut()) .chain(commands.sources.iter_mut()); - for translation_unit in translation_units { - if !translation_unit.processed { - let r = execute_command(compiler, program_data, &translation_unit.args, cache); - translation_unit.execution_result = CommandExecutionResult::from(&r); + for translation_unit_cmd in translation_units { + if translation_unit_cmd.need_to_build { + let r = execute_command(compiler, program_data, &translation_unit_cmd.args, cache); + translation_unit_cmd.execution_result = CommandExecutionResult::from(&r); if let Err(e) = r { cache::save(program_data, cache, commands, test_mode)?; return Err(e); } else if !r.as_ref().unwrap().success() { let err = eyre!( "Ending the program, because the build of: {:?} wasn't ended successfully", - translation_unit.filename + translation_unit_cmd.filename ); cache::save(program_data, cache, commands, test_mode)?; return Err(err); @@ -64,7 +64,7 @@ pub fn run_generated_commands( } if !commands.linker.args.is_empty() { - log::debug!("Executing the main command line..."); // TODO: this refers to the linker + log::debug!("Processing the linker command line..."); let r = execute_command(compiler, program_data, &commands.linker.args, cache); commands.linker.execution_result = CommandExecutionResult::from(&r); @@ -75,7 +75,7 @@ pub fn run_generated_commands( } else if !r.as_ref().unwrap().success() { cache::save(program_data, cache, commands, test_mode)?; return Err(eyre!( - "Ending the program, because the main command line execution wasn't ended successfully", + "Ending the program, because the linker command line execution wasn't ended successfully", )); } } @@ -147,7 +147,7 @@ pub struct SourceCommandLine { pub directory: PathBuf, pub filename: String, pub args: Arguments, - pub processed: bool, + pub need_to_build: bool, pub execution_result: CommandExecutionResult, // TODO an enum with the Kind OF TU that is generating this scl? } @@ -169,11 +169,25 @@ impl SourceCommandLine { directory: tu.path(), filename: tu.file_with_extension(), args, - processed, + need_to_build: !processed, execution_result, } } + pub fn for_translation_unit( + // TODO init it as a args holder, but doesn't have the status yet + tu: impl TranslationUnit, + args: Arguments + ) -> Self { + Self { + directory: tu.path(), + filename: tu.file_with_extension(), + args, + need_to_build: true, + execution_result: CommandExecutionResult::Unprocessed, + } + } + pub fn path(&self) -> PathBuf { self.directory.join(Path::new(&self.filename)) } @@ -202,7 +216,7 @@ impl LinkerCommandLine { } /// Holds the generated command line arguments for a concrete compiler -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Commands { pub compiler: CppCompiler, pub pre_tasks: Vec, // TODO: since there's no really pre-tasks (only build the std_lib), create named entries for std and std.compat diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 5c39dbcf..b1f31e9d 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -33,6 +33,7 @@ pub fn build_project<'a>( ) -> Result { // A registry of the generated command lines let mut commands = Commands::new(model.compiler.cpp_compiler); + // TODO from cache, and find them here instead from the cache // Pre-tasks if model.compiler.cpp_compiler == CppCompiler::GCC && !model.modules.sys_modules.is_empty() { @@ -64,9 +65,10 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: "Building the {:?} C++ standard library implementation", compiler ); - msvc_args::generate_std_cmd_args(model, cache, StdLibMode::Cpp) + msvc_args::generate_std_cmd(model, cache, StdLibMode::Cpp) // TODO move mod msvc_args to commands } else { // TODO: p.ej: existe y además tiene status cached? modificar por &mut + // TODO: no será mejor sacarla de la caché? let source_command_line = SourceCommandLine { directory: built_stdlib_path.file_stem().unwrap().into(), filename: built_stdlib_path @@ -75,7 +77,7 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: .to_string_lossy() .to_string(), args: Arguments::default(), - processed: true, + need_to_build: true, execution_result: CommandExecutionResult::Cached, }; source_command_line @@ -88,7 +90,7 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: "Building the {:?} C ISO standard library implementation", compiler ); - msvc_args::generate_std_cmd_args(model, cache, StdLibMode::CCompat) + msvc_args::generate_std_cmd(model, cache, StdLibMode::CCompat) } else { let source_command_line = SourceCommandLine { directory: built_stdlib_path.file_stem().unwrap().into(), @@ -98,7 +100,7 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: .to_string_lossy() .to_string(), args: Arguments::default(), - processed: true, + need_to_build: true, execution_result: CommandExecutionResult::Cached, }; source_command_line @@ -161,7 +163,27 @@ fn build_sources( /// This function acts like a operation result processor, by running instances /// and parsing the obtained result, handling the flux according to the /// compiler responses> -fn build_modules(model: &ZorkModel, cache: &ZorkCache, commands: &mut Commands) -> Result<()> { +/// +/// TODO: all this fns should be conceptually named and used for something like -> generate_commands_for +fn build_modules(model: &ZorkModel, cache: &mut ZorkCache, commands: &mut Commands) -> Result<()> { + // TODO: the great idea (for all of modules and sources) it to know what of the translation units + // are already on the cache (that means, the commands has already been generated) + // for example: + // TODO: shouldn't be better to check when the project_model is built to use the generated, processed or whatever flag + // and set its command? + + // TODO: so, to summarize, we need something like a cache to Commands mapper or similar + // so, kind of preload the cache for commands. Commands must come pre-filled? + + // TODO: siguiente actualización. En realidad, creo que la idea legendaria no es nada de lo de arriba exactamente. + // Por ejemplo, y si buildeamos en una data-structure un prototype para cada uno de ellos? Así, además de usar el patrón de diseño + // prototype, lo único que hacemos es inyectarle partes concretas, como -> añade los extra args (si son nuevos) y podríamos hacer + // el check todas las veces por cada mínima parte, pero el prototipo podía estar cacheado y solo inyectarle cambios? + // EJ: module_interface_prototype(cpp_compiler...) y puede ser una puta clase en si mismo, con cada cosa detallada, en vez de un + // vector con todo a palo seco (INCLUSO UNA CLASE BUILDER :D) + // revisa la signature de arriba. build_modules -> build_module_interface_command_prototype? Y a la puta cache + // Entonces, después cacheamos todos (como ahora) pero ADEMÁS inyectamos gratis + // let non_tracked_commands_for_translation_units = log::info!("Building the module interfaces and partitions..."); build_module_interfaces(model, cache, &model.modules.interfaces, commands); @@ -175,27 +197,38 @@ fn build_modules(model: &ZorkModel, cache: &ZorkCache, commands: &mut Commands) /// by precompiling the module interface units fn build_module_interfaces<'a>( model: &'a ZorkModel<'_>, - cache: &ZorkCache, + cache: &mut ZorkCache, interfaces: &'a [ModuleInterfaceModel], commands: &mut Commands, ) { interfaces.iter().for_each(|module_interface| { - if !flag_source_file_without_changes(&model.compiler.cpp_compiler, cache, &module_interface.file()) { - sources::generate_module_interfaces_args(model, cache, module_interface, commands); - } else { - let command_line = SourceCommandLine::from_translation_unit( - module_interface, Arguments::default(), true, CommandExecutionResult::Cached - ); + let compiler = cache.compiler; + let lpe = cache.last_program_execution(); + let command_line = if let Some(generated_cmd) = cache.get_cached_module_ifc_cmd(module_interface) { + let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, lpe, generated_cmd, &module_interface.file()); + log::trace!("Source file:{:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_interface.file()); - log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &module_interface.file()); - commands.interfaces.push(command_line); - commands.linker.add_owned_buildable_at(helpers::generate_prebuilt_miu( + if !translation_unit_must_be_rebuilt { log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &module_interface.file()); + } + let mut cached_cmd_line = generated_cmd.clone(); // TODO: somehow, we should manage to solve this on the future + cached_cmd_line.need_to_build = translation_unit_must_be_rebuilt; + commands.linker.add_owned_buildable_at(helpers::generate_prebuilt_miu( // TODO: extremely provisional model.compiler.cpp_compiler, &model.build.output_dir, module_interface - )) - } + )); + cached_cmd_line + } else { + sources::generate_module_interfaces_cmd_args(model, cache, module_interface, commands) + }; + + commands.interfaces.push(command_line); + // commands.linker.add_owned_buildable_at(helpers::generate_prebuilt_miu( + // model.compiler.cpp_compiler, &model.build.output_dir, module_interface + // )) }); } + + /// Parses the configuration in order to compile the module implementation /// translation units declared for the project fn build_module_implementations<'a>( @@ -413,12 +446,12 @@ mod sources { } /// Generates the expected arguments for precompile the BMIs depending on self - pub fn generate_module_interfaces_args<'a>( + pub fn generate_module_interfaces_cmd_args<'a>( model: &'a ZorkModel, cache: &ZorkCache, interface: &'a ModuleInterfaceModel, commands: &mut Commands, - ) { + ) -> SourceCommandLine { let compiler = model.compiler.cpp_compiler; let out_dir = model.build.output_dir.as_ref(); @@ -518,7 +551,9 @@ mod sources { false, CommandExecutionResult::default(), ); - commands.interfaces.push(command_line); + // commands.interfaces.push(command_line); // TODO move commands out of here and later just add the linker + + command_line } /// Generates the expected arguments for compile the implementation module files @@ -776,11 +811,11 @@ mod helpers { /// hasn't been modified since the last build process iteration. /// /// True means 'already processed with previous iteration: Success' and stored on the cache - pub(crate) fn flag_source_file_without_changes( + pub(crate) fn flag_source_file_without_changes( // TODO: kind of `look_for_tu_on_cache_or_generate_command compiler: &CppCompiler, cache: &ZorkCache, file: &Path, - ) -> bool { + ) -> bool { // TODO: it should return an Optional with the SourceCommandLine from the cache if its already there if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { // TODO: Review this // with the new Clang @@ -822,6 +857,45 @@ mod helpers { } } + /// TODO + pub(crate) fn translation_unit_must_be_rebuilt(compiler: CppCompiler, + last_process_execution: &DateTime, + cached_source_cmd: &SourceCommandLine, + file: &Path, +) -> bool { + if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { + log::trace!("Module unit {:?} will be rebuilt since we've detected that you are using Clang in Windows", cached_source_cmd.path()); + return true; + } + + let execution_result = cached_source_cmd.execution_result; + if execution_result != CommandExecutionResult::Success + && execution_result != CommandExecutionResult::Cached + { + log::trace!( + "File {file:?} build process failed previously with status: {:?}. It will be rebuilt again", + execution_result + ); + return true; + }; + + // If exists and was successful, let's see if has been modified after the program last iteration + let file_metadata = file.metadata(); + match file_metadata { + Ok(m) => match m.modified() { + Ok(modified) => DateTime::::from(modified) > *last_process_execution, + Err(e) => { + log::error!("An error happened trying to get the last time that the {file:?} was modified. Processing it anyway because {e:?}"); + true + } + }, + Err(e) => { + log::error!("An error happened trying to retrieve the metadata of {file:?}. Processing it anyway because {e:?}"); + true + } + } + } + pub(crate) fn generate_obj_file_path( compiler: CppCompiler, out_dir: &Path, diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index e9074e61..a8e2954c 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -65,6 +65,7 @@ pub mod worker { let config_files: Vec = find_config_files(project_root, &cli_args.match_files) .with_context(|| "We didn't found a valid Zork++ configuration file")?; log::trace!("Config files found: {config_files:?}"); + // TODO add the last modified time for config_file in config_files { log::debug!( @@ -107,7 +108,7 @@ pub mod worker { cli_args: &'a CliArgs, program_data: &'a ZorkModel<'_>, mut cache: ZorkCache, - ) -> Result { + ) -> Result { // TODO: the return type isn't as clever as it could be let commands: Commands; match cli_args.command { From 6d44a4fee07229dd1b2e7bbe6030239adc3acbe9 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 6 Jun 2024 16:40:03 +0200 Subject: [PATCH 04/73] feat(wip): time for the renormalization of the module implementations --- zork++/src/lib/cache/mod.rs | 17 +++++++++-- zork++/src/lib/compiler/mod.rs | 53 +++++++++++++++++++--------------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 4fb60b5c..b1b01307 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -29,7 +29,7 @@ use serde::{Deserialize, Serialize}; use walkdir::WalkDir; use crate::bounds::TranslationUnit; use crate::cache::compile_commands::CompileCommands; -use crate::project_model::modules::ModuleInterfaceModel; +use crate::project_model::modules::{ModuleImplementationModel, ModuleInterfaceModel}; /// Standalone utility for load from the file system the Zork++ cache file /// for the target [`CppCompiler`] @@ -99,11 +99,24 @@ impl ZorkCache { pub fn last_program_execution(&self) -> &DateTime { &self.last_program_execution } - pub fn get_cached_module_ifc_cmd(&self, module_interface_model: &ModuleInterfaceModel) -> Option<&SourceCommandLine>{ + pub fn get_module_ifc_cmd(&self, module_interface_model: &ModuleInterfaceModel) -> Option<&SourceCommandLine>{ self.generated_commands.interfaces.iter().find(|mi| module_interface_model.file() == (*mi).path() ) } + + pub fn get_module_impl_cmd(&self, module_impl_model: &ModuleImplementationModel) -> Option<&SourceCommandLine>{ + self.generated_commands.implementations.iter().find(|mi| + module_impl_model.file() == (*mi).path() + ) + } + + // pub fn get_source_cmd(&self, module_impl_model: &Source) -> Option<&SourceCommandLine>{ + // self.generated_commands.implementations.iter().find(|mi| + // module_impl_model.file() == (*mi).path() + // ) + // } + /// Returns a [`Option`] of [`CommandDetails`] if the file is persisted already in the cache pub fn is_file_cached(&self, _path: impl AsRef) -> Option<&CommandDetail> { // let last_iteration_details = self.generated_commands.last(); diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index b1f31e9d..5bf40ef4 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -194,7 +194,7 @@ fn build_modules(model: &ZorkModel, cache: &mut ZorkCache, commands: &mut Comman } /// Parses the configuration in order to build the BMIs declared for the project, -/// by precompiling the module interface units +/// by pre-compiling the module interface units fn build_module_interfaces<'a>( model: &'a ZorkModel<'_>, cache: &mut ZorkCache, @@ -206,7 +206,7 @@ fn build_module_interfaces<'a>( let lpe = cache.last_program_execution(); let command_line = if let Some(generated_cmd) = cache.get_cached_module_ifc_cmd(module_interface) { let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, lpe, generated_cmd, &module_interface.file()); - log::trace!("Source file:{:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_interface.file()); + log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_interface.file()); if !translation_unit_must_be_rebuilt { log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &module_interface.file()); } @@ -238,21 +238,26 @@ fn build_module_implementations<'a>( commands: &mut Commands, ) { impls.iter().for_each(|module_impl| { - if !flag_source_file_without_changes(&model.compiler.cpp_compiler, cache, &module_impl.file()) { - sources::generate_module_implementation_args(model, cache, module_impl, commands); - } else { - let command_line = SourceCommandLine::from_translation_unit( - module_impl, Arguments::default(), true, CommandExecutionResult::Cached - ); + let compiler = cache.compiler; + let lpe = cache.last_program_execution(); + let command_line = if let Some(generated_cmd) = cache.get_module_impl_cmd(module_impl) { + let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, lpe, generated_cmd, &module_impl.file()); + log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_impl.file()); - log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &module_impl.file()); - commands.implementations.push(command_line); // TODO:: There's other todo where we - // explain why we should change this code - // (and the other similar ones) - commands.linker.add_owned_buildable_at(helpers::generate_impl_obj_file( - model.compiler.cpp_compiler, &model.build.output_dir, module_impl - )) - } + if !translation_unit_must_be_rebuilt { + log::trace!("Source file: {:?} was not modified since the last iteration. No need to rebuilt it again.", &module_impl.file()); + } + let mut cached_cmd_line = generated_cmd.clone(); // TODO: somehow, we should manage to solve this on the future + cached_cmd_line.need_to_build = translation_unit_must_be_rebuilt; + commands.linker.add_owned_buildable_at(helpers::generate_impl_obj_file( // TODO: extremely provisional + model.compiler.cpp_compiler, &model.build.output_dir, module_impl + )); + cached_cmd_line + } else { + sources::generate_module_implementation_cmd(model, cache, module_impl, commands) + }; + + commands.interfaces.push(command_line); }); } @@ -446,7 +451,7 @@ mod sources { } /// Generates the expected arguments for precompile the BMIs depending on self - pub fn generate_module_interfaces_cmd_args<'a>( + pub fn generate_module_interfaces_cmd<'a>( model: &'a ZorkModel, cache: &ZorkCache, interface: &'a ModuleInterfaceModel, @@ -557,12 +562,12 @@ mod sources { } /// Generates the expected arguments for compile the implementation module files - pub fn generate_module_implementation_args<'a>( + pub fn generate_module_implementation_cmd<'a>( model: &'a ZorkModel, cache: &ZorkCache, implementation: &'a ModuleImplementationModel, commands: &mut Commands, - ) { + ) -> SourceCommandLine { let compiler = model.compiler.cpp_compiler; let out_dir = model.build.output_dir.as_ref(); @@ -645,13 +650,13 @@ mod sources { } } - let command_line = SourceCommandLine::from_translation_unit( + let command_line = SourceCommandLine::for_translation_unit( implementation, - arguments, - false, - CommandExecutionResult::default(), + arguments ); - commands.implementations.push(command_line); + // commands.implementations.push(command_line); + + command_line } } From 226099c25ce1d3544900ad38ff0b42fc880d7dc3 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 7 Jun 2024 08:23:08 +0200 Subject: [PATCH 05/73] feat(wip): saving the job so far --- zork++/src/lib/compiler/mod.rs | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 5bf40ef4..4817f149 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -134,7 +134,7 @@ fn build_sources( commands: &'_ mut Commands, tests: bool, ) -> Result<()> { - log::info!("Building the source files..."); + log::info!("Generating the commands for the source files..."); let srcs = if tests { &model.tests.sourceset.sources } else { @@ -166,14 +166,6 @@ fn build_sources( /// /// TODO: all this fns should be conceptually named and used for something like -> generate_commands_for fn build_modules(model: &ZorkModel, cache: &mut ZorkCache, commands: &mut Commands) -> Result<()> { - // TODO: the great idea (for all of modules and sources) it to know what of the translation units - // are already on the cache (that means, the commands has already been generated) - // for example: - // TODO: shouldn't be better to check when the project_model is built to use the generated, processed or whatever flag - // and set its command? - - // TODO: so, to summarize, we need something like a cache to Commands mapper or similar - // so, kind of preload the cache for commands. Commands must come pre-filled? // TODO: siguiente actualización. En realidad, creo que la idea legendaria no es nada de lo de arriba exactamente. // Por ejemplo, y si buildeamos en una data-structure un prototype para cada uno de ellos? Así, además de usar el patrón de diseño @@ -181,21 +173,18 @@ fn build_modules(model: &ZorkModel, cache: &mut ZorkCache, commands: &mut Comman // el check todas las veces por cada mínima parte, pero el prototipo podía estar cacheado y solo inyectarle cambios? // EJ: module_interface_prototype(cpp_compiler...) y puede ser una puta clase en si mismo, con cada cosa detallada, en vez de un // vector con todo a palo seco (INCLUSO UNA CLASE BUILDER :D) - // revisa la signature de arriba. build_modules -> build_module_interface_command_prototype? Y a la puta cache - // Entonces, después cacheamos todos (como ahora) pero ADEMÁS inyectamos gratis - // let non_tracked_commands_for_translation_units = - log::info!("Building the module interfaces and partitions..."); - build_module_interfaces(model, cache, &model.modules.interfaces, commands); + log::info!("Generating the commands for the module interfaces and partitions..."); + process_module_interfaces(model, cache, &model.modules.interfaces, commands); - log::info!("Building the module implementations..."); - build_module_implementations(model, cache, &model.modules.implementations, commands); + log::info!("Generating the commands for the module implementations and partitions..."); + process_module_implementations(model, cache, &model.modules.implementations, commands); Ok(()) } /// Parses the configuration in order to build the BMIs declared for the project, /// by pre-compiling the module interface units -fn build_module_interfaces<'a>( +fn process_module_interfaces<'a>( model: &'a ZorkModel<'_>, cache: &mut ZorkCache, interfaces: &'a [ModuleInterfaceModel], @@ -204,7 +193,7 @@ fn build_module_interfaces<'a>( interfaces.iter().for_each(|module_interface| { let compiler = cache.compiler; let lpe = cache.last_program_execution(); - let command_line = if let Some(generated_cmd) = cache.get_cached_module_ifc_cmd(module_interface) { + let command_line = if let Some(generated_cmd) = cache.get_module_ifc_cmd(module_interface) { let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, lpe, generated_cmd, &module_interface.file()); log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_interface.file()); @@ -217,7 +206,7 @@ fn build_module_interfaces<'a>( )); cached_cmd_line } else { - sources::generate_module_interfaces_cmd_args(model, cache, module_interface, commands) + sources::generate_module_interface_cmd(model, cache, module_interface, commands) }; commands.interfaces.push(command_line); @@ -231,7 +220,7 @@ fn build_module_interfaces<'a>( /// Parses the configuration in order to compile the module implementation /// translation units declared for the project -fn build_module_implementations<'a>( +fn process_module_implementations<'a>( model: &'a ZorkModel, cache: &ZorkCache, impls: &'a [ModuleImplementationModel], @@ -347,7 +336,7 @@ pub fn generate_main_command_line_args<'a>( arguments.extend(commands.linker.built_files.iter().map(Argument::from)); // TODO can't we avoid this, and just add the pathbufs? commands.linker.args.extend(arguments); - commands.linker.built_files = target + commands.linker.built_files = target // TODO: built_files means raw cpp sources .sourceset() .sources .iter() @@ -451,7 +440,7 @@ mod sources { } /// Generates the expected arguments for precompile the BMIs depending on self - pub fn generate_module_interfaces_cmd<'a>( + pub fn generate_module_interface_cmd<'a>( model: &'a ZorkModel, cache: &ZorkCache, interface: &'a ModuleInterfaceModel, From aac336bc61428fed5224163c75c5b2cfeb7cbe0f Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 21 Jun 2024 18:21:28 +0200 Subject: [PATCH 06/73] feat(wip)!: Creational Flyweights to reduce the memory usage footprint and common Argument(s) that only need to be created once --- zork++/Cargo.lock | 47 ++++++++ zork++/Cargo.toml | 1 + zork++/src/lib/cache/compile_commands.rs | 9 +- zork++/src/lib/cache/mod.rs | 64 +++++++---- zork++/src/lib/cli/output/arguments.rs | 4 +- zork++/src/lib/cli/output/commands.rs | 29 +++-- zork++/src/lib/compiler/clang.rs | 0 zork++/src/lib/compiler/data_factory.rs | 110 +++++++++++++++++++ zork++/src/lib/compiler/mod.rs | 132 ++++++++++++----------- zork++/src/lib/lib.rs | 3 +- zork++/src/lib/project_model/compiler.rs | 6 +- 11 files changed, 300 insertions(+), 105 deletions(-) create mode 100644 zork++/src/lib/compiler/clang.rs create mode 100644 zork++/src/lib/compiler/data_factory.rs diff --git a/zork++/Cargo.lock b/zork++/Cargo.lock index ce34500a..37ffc494 100644 --- a/zork++/Cargo.lock +++ b/zork++/Cargo.lock @@ -388,6 +388,16 @@ dependencies = [ "log", ] +[[package]] +name = "erased-serde" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "errno" version = "0.3.9" @@ -502,6 +512,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inventory" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -827,6 +843,36 @@ dependencies = [ "serde", ] +[[package]] +name = "typeid" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf" + +[[package]] +name = "typetag" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "661d18414ec032a49ece2d56eee03636e43c4e8d577047ab334c0ba892e29aaf" +dependencies = [ + "erased-serde", + "inventory", + "once_cell", + "serde", + "typetag-impl", +] + +[[package]] +name = "typetag-impl" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac73887f47b9312552aa90ef477927ff014d63d1920ca8037c6c1951eab64bb1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -1042,5 +1088,6 @@ dependencies = [ "serde_json", "tempfile", "toml", + "typetag", "walkdir", ] diff --git a/zork++/Cargo.toml b/zork++/Cargo.toml index 2b599124..bb01a3b9 100644 --- a/zork++/Cargo.toml +++ b/zork++/Cargo.toml @@ -18,6 +18,7 @@ path = "src/bin/main.rs" toml = "0.5.11" glob = "0.3.1" serde = { version = "1.0.202", features = ["derive"] } +typetag = "0.2" clap = { version = "4.0.32", features = ["derive"] } log = "0.4.17" env_logger = "0.11.3" diff --git a/zork++/src/lib/cache/compile_commands.rs b/zork++/src/lib/cache/compile_commands.rs index e2fd881e..0e2a189e 100644 --- a/zork++/src/lib/cache/compile_commands.rs +++ b/zork++/src/lib/cache/compile_commands.rs @@ -13,9 +13,11 @@ pub type CompileCommands = Vec; /// Generates the `compile_commands.json` file, that acts as a compilation database /// for some static analysis external tools, like `clang-tidy`, and populates it with /// the generated commands for the translation units -pub(crate) fn map_generated_commands_to_compilation_db(cache: &ZorkCache) -> Result { +pub(crate) fn map_generated_commands_to_compilation_db( + cache: &ZorkCache, +) -> Result { log::trace!("Generating the compilation database..."); - + let generated_commands = cache.get_all_commands_iter(); let mut compilation_db_entries: Vec = Vec::with_capacity(cache.count_total_generated_commands()); @@ -30,9 +32,10 @@ pub(crate) fn map_generated_commands_to_compilation_db(cache: &ZorkCache) -> Res File::create(compile_commands_path) .with_context(|| "Error creating the compilation database")?; } + utils::fs::serialize_object_to_file(Path::new(compile_commands_path), &compilation_db_entries) .with_context(move || "Error saving the compilation database")?; - + Ok(compilation_db_entries) } diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index b1b01307..319c9e76 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -13,6 +13,9 @@ use std::{ path::{Path, PathBuf}, }; +use crate::bounds::TranslationUnit; +use crate::cache::compile_commands::CompileCommands; +use crate::project_model::modules::{ModuleImplementationModel, ModuleInterfaceModel}; use crate::project_model::sourceset::SourceFile; use crate::{ cli::{ @@ -27,9 +30,6 @@ use crate::{ }; use serde::{Deserialize, Serialize}; use walkdir::WalkDir; -use crate::bounds::TranslationUnit; -use crate::cache::compile_commands::CompileCommands; -use crate::project_model::modules::{ModuleImplementationModel, ModuleInterfaceModel}; /// Standalone utility for load from the file system the Zork++ cache file /// for the target [`CppCompiler`] @@ -87,7 +87,7 @@ pub fn save( .with_context(move || "Error saving data to the Zork++ cache") } -#[derive(Serialize, Deserialize, Debug, Default, Clone)] +#[derive(Serialize, Deserialize, Default)] pub struct ZorkCache { pub compiler: CppCompiler, pub last_program_execution: DateTime, @@ -96,19 +96,35 @@ pub struct ZorkCache { } impl ZorkCache { + pub fn new() -> Self { + Self { + compiler: todo!(), + last_program_execution: todo!(), + compilers_metadata: todo!(), + generated_commands: todo!(), + } + } pub fn last_program_execution(&self) -> &DateTime { &self.last_program_execution } - pub fn get_module_ifc_cmd(&self, module_interface_model: &ModuleInterfaceModel) -> Option<&SourceCommandLine>{ - self.generated_commands.interfaces.iter().find(|mi| - module_interface_model.file() == (*mi).path() - ) + pub fn get_module_ifc_cmd( + &self, + module_interface_model: &ModuleInterfaceModel, + ) -> Option<&SourceCommandLine> { + self.generated_commands + .interfaces + .iter() + .find(|mi| module_interface_model.file() == (*mi).path()) } - pub fn get_module_impl_cmd(&self, module_impl_model: &ModuleImplementationModel) -> Option<&SourceCommandLine>{ - self.generated_commands.implementations.iter().find(|mi| - module_impl_model.file() == (*mi).path() - ) + pub fn get_module_impl_cmd( + &self, + module_impl_model: &ModuleImplementationModel, + ) -> Option<&SourceCommandLine> { + self.generated_commands + .implementations + .iter() + .find(|mi| module_impl_model.file() == (*mi).path()) } // pub fn get_source_cmd(&self, module_impl_model: &Source) -> Option<&SourceCommandLine>{ @@ -116,7 +132,7 @@ impl ZorkCache { // module_impl_model.file() == (*mi).path() // ) // } - + /// Returns a [`Option`] of [`CommandDetails`] if the file is persisted already in the cache pub fn is_file_cached(&self, _path: impl AsRef) -> Option<&CommandDetail> { // let last_iteration_details = self.generated_commands.last(); @@ -161,9 +177,11 @@ impl ZorkCache { // { // compile_commands::map_generated_commands_to_compilation_db(self)?; // } - // - if let Some(_new_commands) = self.save_generated_commands(commands, program_data, test_mode) { - if program_data.project.compilation_db { // TODO:: pass the new commands + // + if let Some(_new_commands) = self.save_generated_commands(commands, program_data, test_mode) + { + if program_data.project.compilation_db { + // TODO:: pass the new commands compile_commands::map_generated_commands_to_compilation_db(self)?; } } @@ -199,10 +217,10 @@ impl ZorkCache { // } else { // 1 // }; - + // Generating the compilation database if enabled, and some file has been added, modified // or comes from a previous failure status - + // TODO: oh, fk, I get it finally. We should only regenerate the compilation database if // the generated command line has changed! (which is highly unlikely) // TODO: Create a wrapper enumerated over the Vec, so that we can store in the @@ -213,9 +231,11 @@ impl ZorkCache { // let at_least_one_changed = commands. self.generated_commands = commands; - self.get_all_commands_iter()// TODO: Review the conditions and ensure that are the ones that we're looking for - .any(|cmd| cmd.need_to_build || cmd.execution_result.eq(&CommandExecutionResult::Success)); - + self.get_all_commands_iter() // TODO: Review the conditions and ensure that are the ones that we're looking for + .any(|cmd| { + cmd.need_to_build || cmd.execution_result.eq(&CommandExecutionResult::Success) + }); + // INSTEAD OF THIS, we just can return an Optional with the compilation database, so we can serialize the args in the compile_commands.json // format and then join them in a one-liner string, so they're easy to read and/or copy None @@ -260,7 +280,7 @@ impl ZorkCache { // TODO: read_only_iterator (better name) and docs pls pub fn get_all_commands_iter(&self) -> impl Iterator + Debug + '_ { let generated_commands = &self.generated_commands; - + generated_commands .pre_tasks .iter() diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index a8b6981c..cc542817 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -8,7 +8,7 @@ use std::{borrow::Borrow, ffi::OsStr, path::PathBuf}; use serde::{Deserialize, Serialize}; /// Wrapper type for represent and storing a command line argument -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Argument(String); impl Argument { @@ -144,7 +144,7 @@ pub mod clang_args { // The Windows variant is a Zork++ feature to allow the users to write `import std;` // under -std=c++20 with clang linking against GCC with // some MinGW installation or similar - pub(crate) fn implicit_module_maps(out_dir: &Path) -> Argument { + pub(crate) fn implicit_module_map(out_dir: &Path) -> Argument { if std::env::consts::OS.eq("windows") { Argument::from(format!( "-fmodule-map-file={}", diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index f3aa3f84..2b1357cd 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -1,3 +1,6 @@ +//! Contains helpers and data structures to be processed in a nice and neat way the commands generated to be executed +//! by Zork++ + use std::collections::HashMap; use std::fmt::Debug; use std::slice::Iter; @@ -8,9 +11,7 @@ use std::{ use crate::bounds::TranslationUnit; use crate::cli::output::arguments::Arguments; -/// Contains helpers and data structure to process in -/// a nice and neat way the commands generated to be executed -/// by Zork++ +use crate::compiler::data_factory::{CommonArgs, CompilerCommonArguments}; use crate::{ cache::{self, ZorkCache}, project_model::{compiler::CppCompiler, ZorkModel}, @@ -175,9 +176,9 @@ impl SourceCommandLine { } pub fn for_translation_unit( - // TODO init it as a args holder, but doesn't have the status yet - tu: impl TranslationUnit, - args: Arguments + // TODO init it as a args holder, but doesn't have the status yet + tu: impl TranslationUnit, + args: Arguments, ) -> Self { Self { directory: tu.path(), @@ -216,11 +217,15 @@ impl LinkerCommandLine { } /// Holds the generated command line arguments for a concrete compiler -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Serialize, Deserialize, Default)] pub struct Commands { pub compiler: CppCompiler, pub pre_tasks: Vec, // TODO: since there's no really pre-tasks (only build the std_lib), create named entries for std and std.compat pub system_modules: HashMap, + + pub general_args: CommonArgs, + pub compiler_common_args: Box, + pub interfaces: Vec, pub implementations: Vec, pub sources: Vec, @@ -228,12 +233,16 @@ pub struct Commands { } impl Commands { - pub fn new(compiler: CppCompiler) -> Self { + pub fn new(model: &ZorkModel<'_>, general_args: CommonArgs, compiler_specific_common_args: Box) -> Self { Self { // TODO: try to see if its possible to move around the code and have a From, avoiding default initialization, - // since this will always cause reallocations, and from may be able to allocate at the exact required capacity + // since this will always cause reallocations, and 'from' may be able to allocate at the exact required capacity // of every collection - compiler, + compiler: model.compiler.cpp_compiler, + + general_args, + compiler_common_args: compiler_specific_common_args, + pre_tasks: Vec::with_capacity(0), system_modules: HashMap::with_capacity(0), interfaces: Vec::with_capacity(0), diff --git a/zork++/src/lib/compiler/clang.rs b/zork++/src/lib/compiler/clang.rs new file mode 100644 index 00000000..e69de29b diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs new file mode 100644 index 00000000..a4c076d5 --- /dev/null +++ b/zork++/src/lib/compiler/data_factory.rs @@ -0,0 +1,110 @@ +//! Stores Flyweight data structures that allow to reduce n-plications of arguments for every +//! translation unit, having shared data without replicating it until the final command line must +//! be generated in order to be stored (in cache) and executed (in the underlying shell) + +use std::path::{Path, PathBuf}; + +use serde::{Deserialize, Serialize}; + +use crate::{ + cli::output::arguments::{clang_args, Argument, Arguments}, + project_model::compiler::{CppCompiler, LanguageLevel, StdLib}, + project_model::ZorkModel, +}; + +/// Holds the common arguments across all the different command lines regarding the target compiler +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct CommonArgs { + compiler: CppCompiler, + out_dir: PathBuf, + // out_dir: &'a PathBuf, + language_level: LanguageLevel, + extra_args: Arguments, + // extra_args: &'a [Argument], +} + +impl<'a> From<&'a ZorkModel<'_>> for CommonArgs { + fn from(model: &'a ZorkModel<'_>) -> Self { + let compiler = model.compiler.cpp_compiler; + let out_dir = model.build.output_dir.clone(); + let language_level = model.compiler.cpp_standard; + let extra_args = model.compiler.extra_args.clone(); + + Self { + compiler, + out_dir, + language_level, + extra_args: Arguments::from_vec(extra_args), + } + } +} + +// TODO: the specific ones, like the object file... can we just create a prototype +// function + +/// Allows to have a common interface for any type that represents a data structure which its +/// purpose is to hold common [`Argument`] across the diferent kind of [`TranslationUnit`] +#[typetag::serde(tag = "type")] +pub trait CompilerCommonArguments {} +impl Default for Box { + fn default() -> Self { + Box::new(ClangCommonArgs::default()) // TODO: isn't this a code smell? + } +} +#[typetag::serde] +impl CompilerCommonArguments for ClangCommonArgs {} +#[typetag::serde] +impl CompilerCommonArguments for MsvcCommonArgs {} +#[typetag::serde] +impl CompilerCommonArguments for GccCommonArgs {} + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +pub struct ClangCommonArgs { + // TODO: a HashMap per kind of translation unit which stores the common ones + // for every different kind of translation unit + std_lib: Option, // TODO: technically, should this already be an arg? or should we decouple the + // project model for the Argument(s) type(s)? + implicit_modules: &'static str, + implicit_module_map: Argument, + prebuilt_module_path: Argument, +} +impl ClangCommonArgs { + pub fn new(model: &ZorkModel<'_>) -> Self { + let out_dir: &Path = model.build.output_dir.as_ref(); + + Self { + std_lib: model.compiler.std_lib, // TODO: Argument::from(std_lib) + implicit_modules: "-fimplicit-modules", + implicit_module_map: clang_args::implicit_module_map(out_dir), + prebuilt_module_path: + Argument::from(format!( + "-fprebuilt-module-path={}/clang/modules/interfaces", + out_dir.display() + )) + } + } +} + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +pub struct MsvcCommonArgs { + exception_handling_model: &'static str, + no_logo: &'static str, + no_compile: &'static str, // TODO: should be in the general and pass in the model? + // ref_stdlib: &'static str, // TODO: this are tecnically two args, /reference and the value + // ref_stdlib_compat: &'static str, // TODO: this are tecnically two args, /reference and the value + // TODO: split the dual cases per switches + // TODO: can we have switches like tuples? like switch-value pairs? +} +impl MsvcCommonArgs { + pub fn new() -> Self { + Self { exception_handling_model: "/EHsc", no_logo: "nologo", no_compile: "/c", } + } +} + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +pub struct GccCommonArgs {} +impl GccCommonArgs { + pub fn new() -> Self { + Self {} + } +} diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 4817f149..52905b6c 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -1,11 +1,14 @@ //! The crate responsible for executing the core work of `Zork++`, -// generate command lines and execute them in a shell of the current -// operating system against the designed compilers in the configuration -// file. +//! generate command lines and execute them in a shell of the current +//! operating system against the designed compilers in the configuration +//! file. + +pub mod data_factory; use color_eyre::Result; use std::path::Path; + use crate::bounds::{ExecutableTarget, ExtraArgs, TranslationUnit}; use crate::cli::output::arguments::{clang_args, msvc_args, Arguments}; use crate::cli::output::commands::{CommandExecutionResult, SourceCommandLine}; @@ -22,6 +25,10 @@ use crate::{ }, }; +use self::data_factory::{ + ClangCommonArgs, CommonArgs, CompilerCommonArguments, GccCommonArgs, MsvcCommonArgs, +}; + /// The entry point of the compilation process /// /// Whenever this process gets triggered, the files declared within the @@ -31,19 +38,26 @@ pub fn build_project<'a>( cache: &mut ZorkCache, tests: bool, ) -> Result { + // Generate the Flyweight struct, with the repetitive data and details of the command lines + let general_args = CommonArgs::from(model); + let compiler_specific_common_args: Box = + compiler_common_arguments_factory(model); + // A registry of the generated command lines - let mut commands = Commands::new(model.compiler.cpp_compiler); + let mut commands = Commands::new(model, general_args, compiler_specific_common_args); // TODO from cache, and find them here instead from the cache // Pre-tasks if model.compiler.cpp_compiler == CppCompiler::GCC && !model.modules.sys_modules.is_empty() { helpers::build_sys_modules(model, &mut commands, cache) } + + // TODO: add them to the commands DS, so they are together until they're generated // Build the std library as a module build_modular_stdlib(model, cache, &mut commands); // TODO: ward it with an if for only call this fn for the // 1st - Build the modules - build_modules(model, cache, &mut commands)?; + process_modules(model, cache, &mut commands)?; // 2nd - Build the non module sources build_sources(model, cache, &mut commands, tests)?; // 3rd - Build the executable or the tests @@ -52,6 +66,17 @@ pub fn build_project<'a>( Ok(commands) } +/// Factory function for bring the data structure that holds the common arguments of a source +/// command line for every translation unit, regardeless the underlying choosen compiler +fn compiler_common_arguments_factory(model: &ZorkModel<'_>) -> Box { + // TODO: consider having a union (enum) instead of a fat ptr + match model.compiler.cpp_compiler { + CppCompiler::CLANG => Box::new(ClangCommonArgs::new(model)), + CppCompiler::MSVC => Box::new(MsvcCommonArgs::new()), + CppCompiler::GCC => Box::new(GccCommonArgs::new()), + } +} + /// Builds the C++ standard library as a pre-step acording to the specification /// of each compiler vendor fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: &mut Commands) { @@ -77,30 +102,27 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: .to_string_lossy() .to_string(), args: Arguments::default(), - need_to_build: true, + need_to_build: false, execution_result: CommandExecutionResult::Cached, }; source_command_line }; commands.pre_tasks.push(cpp_stdlib); - let built_stdlib_path = &cache.compilers_metadata.msvc.stdlib_bmi_path; + let built_stdlib_compat_path = &cache.compilers_metadata.msvc.c_stdlib_bmi_path; let c_cpp_stdlib = if !built_stdlib_path.exists() { - log::trace!( - "Building the {:?} C ISO standard library implementation", - compiler - ); + log::trace!("Building the {:?} C compat CPP std lib", compiler); msvc_args::generate_std_cmd(model, cache, StdLibMode::CCompat) } else { let source_command_line = SourceCommandLine { - directory: built_stdlib_path.file_stem().unwrap().into(), - filename: built_stdlib_path + directory: built_stdlib_compat_path.file_stem().unwrap().into(), + filename: built_stdlib_compat_path .file_name() .unwrap() .to_string_lossy() .to_string(), args: Arguments::default(), - need_to_build: true, + need_to_build: false, execution_result: CommandExecutionResult::Cached, }; source_command_line @@ -162,17 +184,8 @@ fn build_sources( /// /// This function acts like a operation result processor, by running instances /// and parsing the obtained result, handling the flux according to the -/// compiler responses> -/// -/// TODO: all this fns should be conceptually named and used for something like -> generate_commands_for -fn build_modules(model: &ZorkModel, cache: &mut ZorkCache, commands: &mut Commands) -> Result<()> { - - // TODO: siguiente actualización. En realidad, creo que la idea legendaria no es nada de lo de arriba exactamente. - // Por ejemplo, y si buildeamos en una data-structure un prototype para cada uno de ellos? Así, además de usar el patrón de diseño - // prototype, lo único que hacemos es inyectarle partes concretas, como -> añade los extra args (si son nuevos) y podríamos hacer - // el check todas las veces por cada mínima parte, pero el prototipo podía estar cacheado y solo inyectarle cambios? - // EJ: module_interface_prototype(cpp_compiler...) y puede ser una puta clase en si mismo, con cada cosa detallada, en vez de un - // vector con todo a palo seco (INCLUSO UNA CLASE BUILDER :D) +/// compiler responses +fn process_modules(model: &ZorkModel, cache: &mut ZorkCache, commands: &mut Commands) -> Result<()> { log::info!("Generating the commands for the module interfaces and partitions..."); process_module_interfaces(model, cache, &model.modules.interfaces, commands); @@ -209,6 +222,7 @@ fn process_module_interfaces<'a>( sources::generate_module_interface_cmd(model, cache, module_interface, commands) }; + // TODO: should we get rid of commands and just store everything on the Cache? commands.interfaces.push(command_line); // commands.linker.add_owned_buildable_at(helpers::generate_prebuilt_miu( // model.compiler.cpp_compiler, &model.build.output_dir, module_interface @@ -216,10 +230,7 @@ fn process_module_interfaces<'a>( }); } - - -/// Parses the configuration in order to compile the module implementation -/// translation units declared for the project +/// Generates the commands for every [`ModuleImplementationModel`] fn process_module_implementations<'a>( model: &'a ZorkModel, cache: &ZorkCache, @@ -245,7 +256,7 @@ fn process_module_implementations<'a>( } else { sources::generate_module_implementation_cmd(model, cache, module_impl, commands) }; - + commands.interfaces.push(command_line); }); } @@ -260,7 +271,7 @@ pub fn generate_main_command_line_args<'a>( log::info!("Generating the main command line..."); let compiler = &model.compiler.cpp_compiler; - let out_dir = model.build.output_dir.as_ref(); + let out_dir: &Path = model.build.output_dir.as_ref(); let executable_name = target.name(); let mut arguments = Arguments::default(); @@ -272,7 +283,7 @@ pub fn generate_main_command_line_args<'a>( CppCompiler::CLANG => { arguments.push_opt(model.compiler.stdlib_arg()); arguments.create_and_push("-fimplicit-modules"); - arguments.push(clang_args::implicit_module_maps(out_dir)); + arguments.push(clang_args::implicit_module_map(out_dir)); arguments.create_and_push(format!( "-fprebuilt-module-path={}", @@ -337,6 +348,8 @@ pub fn generate_main_command_line_args<'a>( commands.linker.args.extend(arguments); commands.linker.built_files = target // TODO: built_files means raw cpp sources + // TODO: add a custom collector on the mod sources + // TODO: just name the field 'sources' .sourceset() .sources .iter() @@ -348,6 +361,8 @@ pub fn generate_main_command_line_args<'a>( /// Specific operations over source files mod sources { + use std::path::Path; + use super::helpers; use crate::bounds::ExtraArgs; use crate::cache::ZorkCache; @@ -391,7 +406,7 @@ mod sources { CppCompiler::CLANG => { arguments.push_opt(model.compiler.stdlib_arg()); arguments.create_and_push("-fimplicit-modules"); - arguments.push(clang_args::implicit_module_maps(out_dir)); + arguments.push(clang_args::implicit_module_map(out_dir)); arguments.push(clang_args::add_prebuilt_module_path(compiler, out_dir)); arguments.create_and_push("-o"); } @@ -447,25 +462,22 @@ mod sources { commands: &mut Commands, ) -> SourceCommandLine { let compiler = model.compiler.cpp_compiler; - let out_dir = model.build.output_dir.as_ref(); + let out_dir: &Path = model.build.output_dir.as_ref(); let mut arguments = Arguments::default(); - arguments.push(model.compiler.language_level_arg()); - arguments.extend_from_slice(model.compiler.extra_args()); - arguments.extend_from_slice(model.modules.extra_args()); match compiler { CppCompiler::CLANG => { - arguments.push_opt(model.compiler.stdlib_arg()); - arguments.create_and_push("-fimplicit-modules"); + // arguments.push_opt(model.compiler.stdlib_arg()); + // arguments.create_and_push("-fimplicit-modules"); arguments.create_and_push("-x"); arguments.create_and_push("c++-module"); arguments.create_and_push("--precompile"); - arguments.push(clang_args::implicit_module_maps(out_dir)); - arguments.create_and_push(format!( + // arguments.push(clang_args::implicit_module_map(out_dir)); + /* arguments.create_and_push(format!( "-fprebuilt-module-path={}/clang/modules/interfaces", out_dir.display() - )); + )); */ clang_args::add_direct_module_interfaces_dependencies( &interface.dependencies, compiler, @@ -539,15 +551,7 @@ mod sources { } } - let command_line = SourceCommandLine::from_translation_unit( - interface, - arguments, - false, - CommandExecutionResult::default(), - ); - // commands.interfaces.push(command_line); // TODO move commands out of here and later just add the linker - - command_line + SourceCommandLine::for_translation_unit(interface, arguments) } /// Generates the expected arguments for compile the implementation module files @@ -570,7 +574,7 @@ mod sources { arguments.push_opt(model.compiler.stdlib_arg()); arguments.create_and_push("-fimplicit-modules"); arguments.create_and_push("-c"); - arguments.push(clang_args::implicit_module_maps(out_dir)); + arguments.push(clang_args::implicit_module_map(out_dir)); // The resultant object file arguments.create_and_push("-o"); @@ -639,10 +643,7 @@ mod sources { } } - let command_line = SourceCommandLine::for_translation_unit( - implementation, - arguments - ); + let command_line = SourceCommandLine::for_translation_unit(implementation, arguments); // commands.implementations.push(command_line); command_line @@ -805,11 +806,13 @@ mod helpers { /// hasn't been modified since the last build process iteration. /// /// True means 'already processed with previous iteration: Success' and stored on the cache - pub(crate) fn flag_source_file_without_changes( // TODO: kind of `look_for_tu_on_cache_or_generate_command + pub(crate) fn flag_source_file_without_changes( + // TODO: kind of `look_for_tu_on_cache_or_generate_command compiler: &CppCompiler, cache: &ZorkCache, file: &Path, - ) -> bool { // TODO: it should return an Optional with the SourceCommandLine from the cache if its already there + ) -> bool { + // TODO: it should return an Optional with the SourceCommandLine from the cache if its already there if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { // TODO: Review this // with the new Clang @@ -852,16 +855,17 @@ mod helpers { } /// TODO - pub(crate) fn translation_unit_must_be_rebuilt(compiler: CppCompiler, - last_process_execution: &DateTime, - cached_source_cmd: &SourceCommandLine, - file: &Path, -) -> bool { + pub(crate) fn translation_unit_must_be_rebuilt( + compiler: CppCompiler, + last_process_execution: &DateTime, + cached_source_cmd: &SourceCommandLine, + file: &Path, + ) -> bool { if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { log::trace!("Module unit {:?} will be rebuilt since we've detected that you are using Clang in Windows", cached_source_cmd.path()); return true; } - + let execution_result = cached_source_cmd.execution_result; if execution_result != CommandExecutionResult::Success && execution_result != CommandExecutionResult::Cached diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index a8e2954c..bc27e26c 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -108,7 +108,8 @@ pub mod worker { cli_args: &'a CliArgs, program_data: &'a ZorkModel<'_>, mut cache: ZorkCache, - ) -> Result { // TODO: the return type isn't as clever as it could be + ) -> Result { + // TODO: the return type isn't as clever as it could be let commands: Commands; match cli_args.command { diff --git a/zork++/src/lib/project_model/compiler.rs b/zork++/src/lib/project_model/compiler.rs index 23826a20..94e59d77 100644 --- a/zork++/src/lib/project_model/compiler.rs +++ b/zork++/src/lib/project_model/compiler.rs @@ -100,10 +100,10 @@ impl CppCompiler { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, Copy)] pub enum LanguageLevel { CPP20, - CPP23, + #[default] CPP23, CPP2A, CPP2B, LATEST, @@ -127,7 +127,7 @@ impl AsRef for LanguageLevel { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] pub enum StdLib { STDLIBCPP, LIBCPP, From 32fcb756585ce567474d4211c4c11493eed84e3b Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sat, 22 Jun 2024 14:07:46 +0200 Subject: [PATCH 07/73] test(wip)!: saving the attributes &str to Cow refactor --- zork++/src/lib/cli/output/arguments.rs | 13 +++ zork++/src/lib/cli/output/commands.rs | 6 +- zork++/src/lib/compiler/data_factory.rs | 40 ++++--- zork++/src/lib/compiler/mod.rs | 7 +- zork++/src/lib/config_file/compiler.rs | 16 +-- zork++/src/lib/config_file/executable.rs | 18 +-- zork++/src/lib/config_file/mod.rs | 4 +- zork++/src/lib/config_file/modules.rs | 52 +++++---- zork++/src/lib/config_file/project.rs | 14 ++- zork++/src/lib/lib.rs | 4 +- zork++/src/lib/project_model/compiler.rs | 33 +++--- zork++/src/lib/project_model/executable.rs | 6 +- zork++/src/lib/project_model/modules.rs | 28 +++-- zork++/src/lib/project_model/project.rs | 8 +- zork++/src/lib/utils/reader.rs | 126 ++++++++++----------- 15 files changed, 214 insertions(+), 161 deletions(-) diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index cc542817..a266c01c 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -1,6 +1,7 @@ //! Types and procedures that represents a command line argument, //! or collections of command line arguments +use std::borrow::Cow; use std::ops::Deref; use std::path::Path; use std::{borrow::Borrow, ffi::OsStr, path::PathBuf}; @@ -23,6 +24,18 @@ impl From<&str> for Argument { } } +impl From> for Argument { + fn from(value: Cow<'_, str>) -> Self { + Self(value.into()) + } +} + +impl From<&Cow<'_, str>> for Argument { + fn from(value: &Cow<'_, str>) -> Self { + Self(value.clone().into()) // TODO: review this + } +} + impl From for Argument { fn from(value: String) -> Argument { Self(value) diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index 2b1357cd..40936946 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -233,7 +233,11 @@ pub struct Commands { } impl Commands { - pub fn new(model: &ZorkModel<'_>, general_args: CommonArgs, compiler_specific_common_args: Box) -> Self { + pub fn new( + model: &ZorkModel<'_>, + general_args: CommonArgs, + compiler_specific_common_args: Box, + ) -> Self { Self { // TODO: try to see if its possible to move around the code and have a From, avoiding default initialization, // since this will always cause reallocations, and 'from' may be able to allocate at the exact required capacity diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index a4c076d5..25d386ee 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -2,7 +2,7 @@ //! translation unit, having shared data without replicating it until the final command line must //! be generated in order to be stored (in cache) and executed (in the underlying shell) -use std::path::{Path, PathBuf}; +use std::{borrow::Cow, path::{Path, PathBuf}}; use serde::{Deserialize, Serialize}; @@ -51,6 +51,9 @@ impl Default for Box { Box::new(ClangCommonArgs::default()) // TODO: isn't this a code smell? } } + +/// TODO: the typetag library doesn't support yet the deserialization of generic impls, only +/// serialization, so there's no point on having any primites #[typetag::serde] impl CompilerCommonArguments for ClangCommonArgs {} #[typetag::serde] @@ -62,42 +65,45 @@ impl CompilerCommonArguments for GccCommonArgs {} pub struct ClangCommonArgs { // TODO: a HashMap per kind of translation unit which stores the common ones // for every different kind of translation unit - std_lib: Option, // TODO: technically, should this already be an arg? or should we decouple the + std_lib: StdLib, // TODO: technically, should this already be an arg? or should we decouple the // project model for the Argument(s) type(s)? - implicit_modules: &'static str, + implicit_modules: Cow<'static, str>, implicit_module_map: Argument, - prebuilt_module_path: Argument, + prebuilt_module_path: Cow<'static, str>, } impl ClangCommonArgs { pub fn new(model: &ZorkModel<'_>) -> Self { let out_dir: &Path = model.build.output_dir.as_ref(); Self { - std_lib: model.compiler.std_lib, // TODO: Argument::from(std_lib) - implicit_modules: "-fimplicit-modules", + std_lib: model.compiler.std_lib.unwrap_or_default(), + implicit_modules: "-fimplicit-modules".into(), implicit_module_map: clang_args::implicit_module_map(out_dir), - prebuilt_module_path: - Argument::from(format!( - "-fprebuilt-module-path={}/clang/modules/interfaces", - out_dir.display() - )) + prebuilt_module_path: Cow::Owned(format!( + "-fprebuilt-module-path={}/clang/modules/interfaces", + out_dir.display() + )), } } } #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct MsvcCommonArgs { - exception_handling_model: &'static str, - no_logo: &'static str, - no_compile: &'static str, // TODO: should be in the general and pass in the model? - // ref_stdlib: &'static str, // TODO: this are tecnically two args, /reference and the value - // ref_stdlib_compat: &'static str, // TODO: this are tecnically two args, /reference and the value + exception_handling_model: Cow<'static, str>, + /* no_logo: &'a str, + no_compile: &'a str, // TODO: should be in the general and pass in the model? */ + // ref_stdlib: &'static str, // TODO: this are tecnically two args, /reference and the value + // ref_stdlib_compat: &'static str, // TODO: this are tecnically two args, /reference and the value // TODO: split the dual cases per switches // TODO: can we have switches like tuples? like switch-value pairs? } impl MsvcCommonArgs { pub fn new() -> Self { - Self { exception_handling_model: "/EHsc", no_logo: "nologo", no_compile: "/c", } + Self { + exception_handling_model: Cow::Borrowed("/EHsc"), + /* no_logo: "nologo", + no_compile: "/c", */ + } } } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 52905b6c..f8ae9c12 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -8,7 +8,6 @@ pub mod data_factory; use color_eyre::Result; use std::path::Path; - use crate::bounds::{ExecutableTarget, ExtraArgs, TranslationUnit}; use crate::cli::output::arguments::{clang_args, msvc_args, Arguments}; use crate::cli::output::commands::{CommandExecutionResult, SourceCommandLine}; @@ -185,7 +184,11 @@ fn build_sources( /// This function acts like a operation result processor, by running instances /// and parsing the obtained result, handling the flux according to the /// compiler responses -fn process_modules(model: &ZorkModel, cache: &mut ZorkCache, commands: &mut Commands) -> Result<()> { +fn process_modules( + model: &ZorkModel, + cache: &mut ZorkCache, + commands: &mut Commands, +) -> Result<()> { log::info!("Generating the commands for the module interfaces and partitions..."); process_module_interfaces(model, cache, &model.modules.interfaces, commands); diff --git a/zork++/src/lib/config_file/compiler.rs b/zork++/src/lib/config_file/compiler.rs index fd1f7f59..eb52d276 100644 --- a/zork++/src/lib/config_file/compiler.rs +++ b/zork++/src/lib/config_file/compiler.rs @@ -1,5 +1,7 @@ //! file for represent the available configuration properties within Zork++ //! for setting up the target compiler +use std::borrow::Cow; + use serde::{Deserialize, Serialize}; use crate::project_model; @@ -68,16 +70,16 @@ use crate::project_model; /// [`zork::config_file::ZorkConfigFile`] doc-test #[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[serde(deny_unknown_fields)] -pub struct CompilerAttribute<'a> { +pub struct CompilerAttribute { pub cpp_compiler: CppCompiler, - #[serde(borrow)] - pub driver_path: Option<&'a str>, + // #[serde(borrow)] + pub driver_path: Option>, pub cpp_standard: LanguageLevel, pub std_lib: Option, - #[serde(borrow)] - pub extra_args: Option>, - #[serde(borrow)] - pub system_headers_path: Option<&'a str>, + // #[serde(borrow)] + pub extra_args: Option>>, + // #[serde(borrow)] + pub system_headers_path: Option>, } /// The C++ compilers available within Zork++ diff --git a/zork++/src/lib/config_file/executable.rs b/zork++/src/lib/config_file/executable.rs index 4c55924a..5a81ac95 100644 --- a/zork++/src/lib/config_file/executable.rs +++ b/zork++/src/lib/config_file/executable.rs @@ -1,4 +1,6 @@ //! Specify the execution configuration +use std::borrow::Cow; + use serde::*; /// [`ExecutableAttribute`] - The core section to instruct the compiler to work with C++20 modules. @@ -42,12 +44,12 @@ use serde::*; #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] #[serde(deny_unknown_fields)] pub struct ExecutableAttribute<'a> { - #[serde(borrow)] - pub executable_name: Option<&'a str>, - #[serde(borrow)] - pub sources_base_path: Option<&'a str>, - #[serde(borrow)] - pub sources: Option>, - #[serde(borrow)] - pub extra_args: Option>, + // #[serde(borrow)] + pub executable_name: Option>, + // #[serde(borrow)] + pub sources_base_path: Option>, + // #[serde(borrow)] + pub sources: Option>>, + // #[serde(borrow)] + pub extra_args: Option>>, } diff --git a/zork++/src/lib/config_file/mod.rs b/zork++/src/lib/config_file/mod.rs index dbc9c994..2a4c4d33 100644 --- a/zork++/src/lib/config_file/mod.rs +++ b/zork++/src/lib/config_file/mod.rs @@ -46,8 +46,8 @@ use self::{ pub struct ZorkConfigFile<'a> { #[serde(borrow)] pub project: ProjectAttribute<'a>, - #[serde(borrow)] - pub compiler: CompilerAttribute<'a>, + // #[serde(borrow)] + pub compiler: CompilerAttribute, #[serde(borrow)] pub build: Option>, #[serde(borrow)] diff --git a/zork++/src/lib/config_file/modules.rs b/zork++/src/lib/config_file/modules.rs index 25727d7a..a87f4227 100644 --- a/zork++/src/lib/config_file/modules.rs +++ b/zork++/src/lib/config_file/modules.rs @@ -1,4 +1,6 @@ //!! The core section to instruct the compiler to work with C++20 modules. The most important are the base path to the interfaces and implementation files +use std::borrow::Cow; + use serde::{Deserialize, Serialize}; /// [`ModulesAttribute`] - The core section to instruct the compiler to work with C++20 modules. The most important are the base path to the interfaces and implementation files @@ -61,18 +63,18 @@ use serde::{Deserialize, Serialize}; /// ``` #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct ModulesAttribute<'a> { - #[serde(borrow)] - pub base_ifcs_dir: Option<&'a str>, - #[serde(borrow)] + // #[serde(borrow)] + pub base_ifcs_dir: Option>, + // #[serde(borrow)] pub interfaces: Option>>, - #[serde(borrow)] - pub base_impls_dir: Option<&'a str>, - #[serde(borrow)] + // #[serde(borrow)] + pub base_impls_dir: Option>, + // #[serde(borrow)] pub implementations: Option>>, - #[serde(borrow)] - pub sys_modules: Option>, - #[serde(borrow)] - pub extra_args: Option>, + // #[serde(borrow)] + pub sys_modules: Option>>, + // #[serde(borrow)] + pub extra_args: Option>>, } /// [`ModuleInterface`] - A module interface structure for dealing @@ -135,14 +137,14 @@ pub struct ModulesAttribute<'a> { #[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(deny_unknown_fields)] pub struct ModuleInterface<'a> { - #[serde(borrow)] - pub file: &'a str, - #[serde(borrow)] - pub module_name: Option<&'a str>, - #[serde(borrow)] + // #[serde(borrow)] + pub file: Cow<'a, str>, + // #[serde(borrow)] + pub module_name: Option>, + // #[serde(borrow)] pub partition: Option>, - #[serde(borrow)] - pub dependencies: Option>, + // #[serde(borrow)] + pub dependencies: Option>>, } /// [`ModulePartition`] - Type for dealing with the parse work @@ -160,10 +162,10 @@ pub struct ModuleInterface<'a> { /// This option only takes effect with MSVC #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct ModulePartition<'a> { - #[serde(borrow)] - pub module: &'a str, - #[serde(borrow)] - pub partition_name: Option<&'a str>, + // #[serde(borrow)] + pub module: Cow<'a, str>, + // #[serde(borrow)] + pub partition_name: Option>, pub is_internal_partition: Option, } @@ -202,8 +204,8 @@ pub struct ModulePartition<'a> { /// ``` #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct ModuleImplementation<'a> { - #[serde(borrow)] - pub file: &'a str, - #[serde(borrow)] - pub dependencies: Option>, + // #[serde(borrow)] + pub file: Cow<'a, str>, + // #[serde(borrow)] + pub dependencies: Option>>, } diff --git a/zork++/src/lib/config_file/project.rs b/zork++/src/lib/config_file/project.rs index edc22c1c..6e7765cd 100644 --- a/zork++/src/lib/config_file/project.rs +++ b/zork++/src/lib/config_file/project.rs @@ -1,5 +1,7 @@ //! Metadata about the user's project +use std::borrow::Cow; + use serde::{Deserialize, Serialize}; /// [`ProjectAttribute`] - Metadata about the user's project @@ -39,11 +41,11 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[serde(deny_unknown_fields)] pub struct ProjectAttribute<'a> { - #[serde(borrow)] - pub name: &'a str, - #[serde(borrow)] - pub authors: Option>, + // #[serde(borrow)] + pub name: Cow<'a, str>, + // #[serde(borrow)] + pub authors: Option>>, pub compilation_db: Option, - #[serde(borrow)] - pub project_root: Option<&'a str>, + // #[serde(borrow)] + pub project_root: Option>, } diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index bc27e26c..8a1bec2b 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -82,7 +82,7 @@ pub mod worker { let config = config_file::zork_cfg_from_file(raw_file.as_str()) .with_context(|| "Could not parse configuration file")?; - let program_data = build_model(&config, cli_args, &abs_project_root)?; + let program_data = build_model(config, cli_args, &abs_project_root)?; create_output_directory(&program_data)?; // TODO avoid this call without check if exists let cache = cache::load(&program_data, cli_args) @@ -226,7 +226,7 @@ pub mod worker { .replace('\\', "/"); let zcf: ZorkConfigFile = config_file::zork_cfg_from_file(&normalized_cfg_file)?; let cli_args = CliArgs::parse_from(["", "-vv", "run"]); - let model = build_model(&zcf, &cli_args, temp_path) + let model = build_model(zcf, &cli_args, temp_path) .with_context(|| "Error building the project model")?; // This should create and out/ directory in the ./zork++ folder at the root of this project diff --git a/zork++/src/lib/project_model/compiler.rs b/zork++/src/lib/project_model/compiler.rs index 94e59d77..e31138c7 100644 --- a/zork++/src/lib/project_model/compiler.rs +++ b/zork++/src/lib/project_model/compiler.rs @@ -1,4 +1,5 @@ use core::fmt; +use std::borrow::Cow; use serde::{Deserialize, Serialize}; @@ -7,20 +8,23 @@ use crate::{bounds::ExtraArgs, cli::output::arguments::Argument}; #[derive(Debug, PartialEq, Eq)] pub struct CompilerModel<'a> { pub cpp_compiler: CppCompiler, - pub driver_path: &'a str, + pub driver_path: Cow<'a, str>, pub cpp_standard: LanguageLevel, pub std_lib: Option, pub extra_args: Vec, } impl<'a> CompilerModel<'a> { - pub fn language_level_arg(&self) -> Argument { + pub fn language_level(&self) -> Cow<'static, str> { match self.cpp_compiler { - CppCompiler::CLANG | CppCompiler::GCC => { - Argument::from(format!("-std=c++{}", self.cpp_standard)) - } - CppCompiler::MSVC => Argument::from(format!("/std:c++{}", self.cpp_standard)), - } + CppCompiler::CLANG | CppCompiler::GCC => + format!("-std=c++{}", self.cpp_standard), + CppCompiler::MSVC => format!("/std:c++{}", self.cpp_standard), + }.into() + } + + pub fn language_level_arg(&self) -> Argument { + Argument::from(self.language_level()) } pub fn stdlib_arg(&self) -> Option { @@ -63,15 +67,15 @@ impl AsRef for CppCompiler { impl CppCompiler { /// Returns an &str representing the compiler driver that will be called /// in the command line to generate the build events - pub fn get_driver<'a>(&self, compiler_model: &'a CompilerModel) -> &'a str { + pub fn get_driver<'a>(&self, compiler_model: &'a CompilerModel) -> Cow<'a, str> { if !compiler_model.driver_path.is_empty() { - compiler_model.driver_path + Cow::Borrowed(&compiler_model.driver_path) } else { - match *self { + Cow::Borrowed(match *self { CppCompiler::CLANG => "clang++", CppCompiler::MSVC => "cl", CppCompiler::GCC => "g++", - } + }) } } @@ -103,7 +107,8 @@ impl CppCompiler { #[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, Copy)] pub enum LanguageLevel { CPP20, - #[default] CPP23, + #[default] + CPP23, CPP2A, CPP2B, LATEST, @@ -127,10 +132,10 @@ impl AsRef for LanguageLevel { } } -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] pub enum StdLib { STDLIBCPP, - LIBCPP, + #[default] LIBCPP, } impl fmt::Display for StdLib { diff --git a/zork++/src/lib/project_model/executable.rs b/zork++/src/lib/project_model/executable.rs index 762bf152..010b2a6c 100644 --- a/zork++/src/lib/project_model/executable.rs +++ b/zork++/src/lib/project_model/executable.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use crate::{ bounds::{ExecutableTarget, ExtraArgs}, cli::output::arguments::Argument, @@ -7,7 +9,7 @@ use super::sourceset::SourceSet; #[derive(Debug, PartialEq, Eq)] pub struct ExecutableModel<'a> { - pub executable_name: &'a str, + pub executable_name: Cow<'a, str>, pub sourceset: SourceSet, pub extra_args: Vec, } @@ -20,7 +22,7 @@ impl<'a> ExtraArgs<'a> for ExecutableModel<'a> { impl<'a> ExecutableTarget<'a> for ExecutableModel<'a> { fn name(&'a self) -> &'a str { - self.executable_name + self.executable_name.as_ref() } fn sourceset(&'a self) -> &'a SourceSet { &self.sourceset diff --git a/zork++/src/lib/project_model/modules.rs b/zork++/src/lib/project_model/modules.rs index 2466b055..74e46894 100644 --- a/zork++/src/lib/project_model/modules.rs +++ b/zork++/src/lib/project_model/modules.rs @@ -1,4 +1,5 @@ use core::fmt; +use std::borrow::Cow; use std::path::{Path, PathBuf}; use crate::bounds::ExtraArgs; @@ -11,7 +12,7 @@ pub struct ModulesModel<'a> { pub interfaces: Vec>, pub base_impls_dir: &'a Path, pub implementations: Vec>, - pub sys_modules: Vec<&'a str>, + pub sys_modules: Vec>, pub extra_args: Vec, } @@ -26,9 +27,9 @@ pub struct ModuleInterfaceModel<'a> { pub path: PathBuf, pub file_stem: String, pub extension: String, - pub module_name: &'a str, + pub module_name: Cow<'a, str>, pub partition: Option>, - pub dependencies: Vec<&'a str>, + pub dependencies: Vec>, } impl<'a> fmt::Display for ModuleInterfaceModel<'a> { @@ -85,13 +86,14 @@ impl<'a> TranslationUnit for &'a ModuleInterfaceModel<'a> { #[derive(Debug, PartialEq, Eq)] pub struct ModulePartitionModel<'a> { - pub module: &'a str, - pub partition_name: &'a str, + pub module: Cow<'a, str>, + pub partition_name: Cow<'a, str>, pub is_internal_partition: bool, } -impl<'a> From<&ModulePartition<'a>> for ModulePartitionModel<'a> { - fn from(value: &ModulePartition<'a>) -> Self { + +impl<'a> From> for ModulePartitionModel<'a> { + fn from(value: ModulePartition<'a>) -> Self { Self { module: value.module, partition_name: value.partition_name.unwrap_or_default(), @@ -100,12 +102,22 @@ impl<'a> From<&ModulePartition<'a>> for ModulePartitionModel<'a> { } } +/* impl<'a> From<&ModulePartition<'a>> for ModulePartitionModel<'a> { + fn from(value: &ModulePartition<'a>) -> Self { + Self { + module: value.module, + partition_name: value.partition_name.unwrap_or_default(), + is_internal_partition: value.is_internal_partition.unwrap_or_default(), + } + } +} */ + #[derive(Debug, PartialEq, Eq)] pub struct ModuleImplementationModel<'a> { pub path: PathBuf, pub file_stem: String, pub extension: String, - pub dependencies: Vec<&'a str>, + pub dependencies: Vec>, } impl<'a> fmt::Display for ModuleImplementationModel<'a> { diff --git a/zork++/src/lib/project_model/project.rs b/zork++/src/lib/project_model/project.rs index 4f3f0653..7c8a3c7a 100644 --- a/zork++/src/lib/project_model/project.rs +++ b/zork++/src/lib/project_model/project.rs @@ -1,7 +1,9 @@ +use std::borrow::Cow; + #[derive(Debug, PartialEq, Eq)] pub struct ProjectModel<'a> { - pub name: &'a str, - pub authors: &'a [&'a str], + pub name: Cow<'a, str>, + pub authors: &'a [Cow<'a, str>], pub compilation_db: bool, - pub project_root: Option<&'a str>, + pub project_root: Option>, } diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index b628dac4..4e380b25 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -26,6 +26,7 @@ use crate::{ utils, }; use color_eyre::{eyre::eyre, Result}; +use std::borrow::Cow; use std::path::{Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; @@ -90,18 +91,17 @@ pub fn find_config_files( } pub fn build_model<'a>( - config: &'a ZorkConfigFile, + config: ZorkConfigFile<'a>, cli_args: &'a CliArgs, absolute_project_root: &Path, ) -> Result> { - let project = assemble_project_model(&config.project); + let project = assemble_project_model(config.project); - let compiler = assemble_compiler_model(&config.compiler, cli_args); - let build = assemble_build_model(&config.build, absolute_project_root); - let executable = - assemble_executable_model(project.name, &config.executable, absolute_project_root); - let modules = assemble_modules_model(&config.modules, absolute_project_root); - let tests = assemble_tests_model(project.name, &config.tests, absolute_project_root); + let compiler = assemble_compiler_model(config.compiler, cli_args); + let build = assemble_build_model(config.build, absolute_project_root); + let executable = assemble_executable_model(&project.name, config.executable, absolute_project_root); + let modules = assemble_modules_model(config.modules, absolute_project_root); + let tests = assemble_tests_model(project.name.as_ref(), config.tests, absolute_project_root); Ok(ZorkModel { project, @@ -113,36 +113,34 @@ pub fn build_model<'a>( }) } -fn assemble_project_model<'a>(config: &'a ProjectAttribute) -> ProjectModel<'a> { +fn assemble_project_model<'a>(config: ProjectAttribute<'a>) -> ProjectModel<'a> { ProjectModel { name: config.name, authors: config .authors - .as_ref() - .map_or_else(|| &[] as &[&str], |auths| auths.as_slice()), + // .as_ref() + // .map_or_else(|| &[] as &[Cow<'a, str>], |auths| auths.as_slice()), + .map_or_else(|| &[] as &[Cow<'a, str>], |auths| auths.as_slice()), compilation_db: config.compilation_db.unwrap_or_default(), project_root: config.project_root, } } fn assemble_compiler_model<'a>( - config: &'a CompilerAttribute, + config: CompilerAttribute, cli_args: &'a CliArgs, ) -> CompilerModel<'a> { let extra_args = config .extra_args - .as_ref() - .map(|args| args.iter().map(|arg| Argument::from(*arg)).collect()) + .map(|args| args.into_iter().map(|arg| Argument::from(arg)).collect()) .unwrap_or_default(); - let cli_driver_path = cli_args.driver_path.as_ref(); - CompilerModel { cpp_compiler: config.cpp_compiler.clone().into(), - driver_path: if let Some(driver_path) = cli_driver_path { - driver_path.as_str() + driver_path: if let Some(driver_path) = cli_args.driver_path.as_ref() { + Cow::Borrowed(driver_path) } else { - config.driver_path.unwrap_or_default() + Cow::Owned(cli_args.driver_path.clone().unwrap_or_default()) // TODO: review this }, cpp_standard: config.cpp_standard.clone().into(), std_lib: config.std_lib.clone().map(|lib| lib.into()), @@ -150,7 +148,7 @@ fn assemble_compiler_model<'a>( } } -fn assemble_build_model(config: &Option, project_root: &Path) -> BuildModel { +fn assemble_build_model(config: Option, project_root: &Path) -> BuildModel { let output_dir = config .as_ref() .and_then(|build| build.output_dir) @@ -164,15 +162,16 @@ fn assemble_build_model(config: &Option, project_root: &Path) -> //noinspection ALL fn assemble_executable_model<'a>( - project_name: &'a str, - config: &'a Option, + project_name: &'a Cow<'a, str>, + config: Option>, project_root: &Path, ) -> ExecutableModel<'a> { let config = config.as_ref(); let executable_name = config - .and_then(|exe| exe.executable_name) - .unwrap_or(project_name); + .as_ref() + .and_then(|exe| -> Option> {exe.executable_name.clone()}) + .unwrap_or(Cow::Borrowed(project_name)); let sources = config .and_then(|exe| exe.sources.clone()) @@ -182,7 +181,7 @@ fn assemble_executable_model<'a>( let extra_args = config .and_then(|exe| exe.extra_args.as_ref()) - .map(|args| args.iter().map(|arg| Argument::from(*arg)).collect()) + .map(|args| args.iter().map(|arg| Argument::from(arg)).collect()) .unwrap_or_default(); ExecutableModel { @@ -193,27 +192,25 @@ fn assemble_executable_model<'a>( } fn assemble_modules_model<'a>( - config: &'a Option, + config: Option>, project_root: &Path, ) -> ModulesModel<'a> { - let config = config.as_ref(); - let base_ifcs_dir = config .and_then(|modules| modules.base_ifcs_dir) - .unwrap_or("."); + .unwrap_or(Cow::Borrowed(".")); let interfaces = config .and_then(|modules| modules.interfaces.as_ref()) .map(|ifcs| { ifcs.iter() - .map(|m_ifc| assemble_module_interface_model(m_ifc, base_ifcs_dir, project_root)) + .map(|m_ifc| -> ModuleInterfaceModel<'_> {assemble_module_interface_model(m_ifc, base_ifcs_dir, project_root)}) .collect() }) .unwrap_or_default(); let base_impls_dir = config .and_then(|modules| modules.base_impls_dir) - .unwrap_or("."); + .unwrap_or(Cow::Borrowed(".")); let implementations = config .and_then(|modules| modules.implementations.as_ref()) @@ -221,7 +218,7 @@ fn assemble_modules_model<'a>( impls .iter() .map(|m_impl| { - assemble_module_implementation_model(m_impl, base_impls_dir, project_root) + assemble_module_implementation_model(m_impl, &base_impls_dir, project_root) }) .collect() }) @@ -237,9 +234,9 @@ fn assemble_modules_model<'a>( .unwrap_or_default(); ModulesModel { - base_ifcs_dir: Path::new(base_ifcs_dir), + base_ifcs_dir: Path::new(&base_ifcs_dir), interfaces, - base_impls_dir: Path::new(base_impls_dir), + base_impls_dir: Path::new(&base_impls_dir), implementations, sys_modules, extra_args, @@ -253,20 +250,18 @@ fn assemble_module_interface_model<'a>( ) -> ModuleInterfaceModel<'a> { let file_path = Path::new(project_root).join(base_path).join(config.file); let module_name = config.module_name.unwrap_or_else(|| { - Path::new(config.file) + std::borrow::Cow::Borrowed(Path::new(&config.file) .file_stem() .unwrap_or_else(|| panic!("Found ill-formed path on: {}", config.file)) .to_str() - .unwrap() + .unwrap()) }); let dependencies = config.dependencies.clone().unwrap_or_default(); let partition = if config.partition.is_none() { None } else { - Some(ModulePartitionModel::from( - config.partition.as_ref().unwrap(), - )) + Some(ModulePartitionModel::from( config.partition.unwrap(),)) }; let file_details = utils::fs::get_file_details(&file_path).unwrap_or_else(|_| { @@ -283,19 +278,20 @@ fn assemble_module_interface_model<'a>( } fn assemble_module_implementation_model<'a>( - config: &'a ModuleImplementation, + config: ModuleImplementation<'a>, base_path: &str, project_root: &Path, ) -> ModuleImplementationModel<'a> { - let file_path = Path::new(project_root).join(base_path).join(config.file); - let mut dependencies = config.dependencies.clone().unwrap_or_default(); + let mut dependencies = config.dependencies.unwrap_or_default(); + + let file_path = Path::new(project_root).join(base_path).join(config.file.as_ref()); if dependencies.is_empty() { - let last_dot_index = config.file.rfind('.'); + let last_dot_index = config.file.as_ref().rfind('.'); if let Some(idx) = last_dot_index { let implicit_dependency = config.file.split_at(idx); - dependencies.push(implicit_dependency.0) + dependencies.push(Cow::Borrowed(implicit_dependency.0)) } else { - dependencies.push(config.file); + dependencies.push(Cow::Borrowed(&config.file)); } } @@ -313,7 +309,7 @@ fn assemble_module_implementation_model<'a>( fn assemble_tests_model<'a>( project_name: &'a str, - config: &'a Option, + config: Option, project_root: &Path, ) -> TestsModel { let config = config.as_ref(); @@ -340,11 +336,11 @@ fn assemble_tests_model<'a>( } } -fn get_sourceset_for(srcs: Vec<&str>, project_root: &Path) -> SourceSet { +fn get_sourceset_for(srcs: Vec>, project_root: &Path) -> SourceSet { let sources = srcs .iter() .map(|src| { - let target_src = project_root.join(src); + let target_src = project_root.join(src.as_ref()); if src.contains('*') { Source::Glob(GlobPattern(target_src)) } else { @@ -373,6 +369,8 @@ fn get_sourceset_for(srcs: Vec<&str>, project_root: &Path) -> SourceSet { #[cfg(test)] mod test { + use std::borrow::Cow; + use crate::config_file; use crate::utils::fs; use crate::{ @@ -398,18 +396,18 @@ mod test { let config: ZorkConfigFile = config_file::zork_cfg_from_file(CONFIG_FILE_MOCK)?; let cli_args = CliArgs::parse_from(["", "-vv", "run"]); let abs_path_for_mock = fs::get_project_root_absolute_path(Path::new("."))?; - let model = build_model(&config, &cli_args, &abs_path_for_mock); + let model = build_model(config, &cli_args, &abs_path_for_mock); let expected = ZorkModel { project: ProjectModel { - name: "Zork++", - authors: &["zerodaycode.gz@gmail.com"], + name: "Zork++".into(), + authors: &["zerodaycode.gz@gmail.com".into()], compilation_db: false, project_root: None, }, compiler: CompilerModel { cpp_compiler: CppCompiler::CLANG, - driver_path: "", + driver_path: Cow::Borrowed(""), cpp_standard: LanguageLevel::CPP20, std_lib: None, extra_args: vec![], @@ -418,7 +416,7 @@ mod test { output_dir: abs_path_for_mock.join("out"), }, executable: ExecutableModel { - executable_name: "Zork++", + executable_name: "Zork++".into(), sourceset: SourceSet { sources: vec![] }, extra_args: vec![], }, @@ -431,7 +429,7 @@ mod test { extra_args: vec![], }, tests: TestsModel { - test_executable_name: "Zork++_test".to_string(), + test_executable_name: "Zork++_test".into(), sourceset: SourceSet { sources: vec![] }, extra_args: vec![], }, @@ -448,18 +446,18 @@ mod test { config_file::zork_cfg_from_file(utils::constants::CONFIG_FILE_MOCK)?; let cli_args = CliArgs::parse_from(["", "-vv", "run"]); let abs_path_for_mock = fs::get_project_root_absolute_path(Path::new("."))?; - let model = build_model(&config, &cli_args, &abs_path_for_mock); + let model = build_model(config, &cli_args, &abs_path_for_mock); let expected = ZorkModel { project: ProjectModel { - name: "Zork++", - authors: &["zerodaycode.gz@gmail.com"], + name: "Zork++".into(), + authors: &["zerodaycode.gz@gmail.com".into()], compilation_db: true, project_root: None, }, compiler: CompilerModel { cpp_compiler: CppCompiler::CLANG, - driver_path: "", + driver_path: Cow::Borrowed(""), cpp_standard: LanguageLevel::CPP2B, std_lib: Some(StdLib::LIBCPP), extra_args: vec![Argument::from("-Wall")], @@ -468,7 +466,7 @@ mod test { output_dir: abs_path_for_mock.clone(), }, executable: ExecutableModel { - executable_name: "zork", + executable_name: "zork".into(), sourceset: SourceSet { sources: vec![] }, extra_args: vec![Argument::from("-Werr")], }, @@ -479,7 +477,7 @@ mod test { path: abs_path_for_mock.join("ifcs"), file_stem: String::from("maths"), extension: String::from("cppm"), - module_name: "maths", + module_name: "maths".into(), partition: None, dependencies: vec![], }, @@ -487,7 +485,7 @@ mod test { path: abs_path_for_mock.join("ifcs"), file_stem: String::from("some_module"), extension: String::from("cppm"), - module_name: "maths", + module_name: "maths".into(), partition: None, dependencies: vec![], }, @@ -498,16 +496,16 @@ mod test { path: abs_path_for_mock.join("srcs"), file_stem: String::from("maths"), extension: String::from("cpp"), - dependencies: vec!["maths"], + dependencies: vec!["maths".into()], }, ModuleImplementationModel { path: abs_path_for_mock.join("srcs"), file_stem: String::from("some_module_impl"), extension: String::from("cpp"), - dependencies: vec!["iostream"], + dependencies: vec!["iostream".into()], }, ], - sys_modules: vec!["iostream"], + sys_modules: vec!["iostream".into()], extra_args: vec![Argument::from("-Wall")], }, tests: TestsModel { From 79e9e374975867f77a5dbaac0d5fccbf5ed9a111 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 23 Jun 2024 12:02:46 +0200 Subject: [PATCH 08/73] test: saving the refactor of the cfg and model data structures being Cow based --- zork++/src/lib/project_model/modules.rs | 8 +++---- zork++/src/lib/utils/reader.rs | 29 +++++++++++++------------ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/zork++/src/lib/project_model/modules.rs b/zork++/src/lib/project_model/modules.rs index 74e46894..0f68235a 100644 --- a/zork++/src/lib/project_model/modules.rs +++ b/zork++/src/lib/project_model/modules.rs @@ -102,15 +102,15 @@ impl<'a> From> for ModulePartitionModel<'a> { } } -/* impl<'a> From<&ModulePartition<'a>> for ModulePartitionModel<'a> { - fn from(value: &ModulePartition<'a>) -> Self { +impl<'a> From<&ModulePartition<'a>> for ModulePartitionModel<'a> { + fn from(value: &ModulePartition<'a>) -> &'a Self { Self { - module: value.module, + module: value.module., partition_name: value.partition_name.unwrap_or_default(), is_internal_partition: value.is_internal_partition.unwrap_or_default(), } } -} */ +} #[derive(Debug, PartialEq, Eq)] pub struct ModuleImplementationModel<'a> { diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 4e380b25..5a7dd66c 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -95,11 +95,12 @@ pub fn build_model<'a>( cli_args: &'a CliArgs, absolute_project_root: &Path, ) -> Result> { + let proj_name = config.project.name.clone(); let project = assemble_project_model(config.project); let compiler = assemble_compiler_model(config.compiler, cli_args); let build = assemble_build_model(config.build, absolute_project_root); - let executable = assemble_executable_model(&project.name, config.executable, absolute_project_root); + let executable = assemble_executable_model(proj_name, config.executable, absolute_project_root); let modules = assemble_modules_model(config.modules, absolute_project_root); let tests = assemble_tests_model(project.name.as_ref(), config.tests, absolute_project_root); @@ -120,7 +121,7 @@ fn assemble_project_model<'a>(config: ProjectAttribute<'a>) -> ProjectModel<'a> .authors // .as_ref() // .map_or_else(|| &[] as &[Cow<'a, str>], |auths| auths.as_slice()), - .map_or_else(|| &[] as &[Cow<'a, str>], |auths| auths.as_slice()), + .map_or_else(|| &[] as [Cow<'a, str>], |auths| &*auths), compilation_db: config.compilation_db.unwrap_or_default(), project_root: config.project_root, } @@ -162,20 +163,19 @@ fn assemble_build_model(config: Option, project_root: &Path) -> //noinspection ALL fn assemble_executable_model<'a>( - project_name: &'a Cow<'a, str>, + project_name: &Cow<'a, str>, config: Option>, project_root: &Path, ) -> ExecutableModel<'a> { let config = config.as_ref(); let executable_name = config - .as_ref() .and_then(|exe| -> Option> {exe.executable_name.clone()}) - .unwrap_or(Cow::Borrowed(project_name)); + .unwrap_or_else(|| project_name.clone()); let sources = config .and_then(|exe| exe.sources.clone()) - .unwrap_or_default(); + .unwrap_or_else(|| Vec::with_capacity(0)); let sourceset = get_sourceset_for(sources, project_root); @@ -196,6 +196,7 @@ fn assemble_modules_model<'a>( project_root: &Path, ) -> ModulesModel<'a> { let base_ifcs_dir = config + .as_ref() .and_then(|modules| modules.base_ifcs_dir) .unwrap_or(Cow::Borrowed(".")); @@ -248,20 +249,20 @@ fn assemble_module_interface_model<'a>( base_path: &str, project_root: &Path, ) -> ModuleInterfaceModel<'a> { - let file_path = Path::new(project_root).join(base_path).join(config.file); - let module_name = config.module_name.unwrap_or_else(|| { - std::borrow::Cow::Borrowed(Path::new(&config.file) + let file_path = Path::new(project_root).join(base_path).join(config.file.as_ref()); + let module_name = config.module_name.clone().unwrap_or_else(|| { + Cow::Borrowed(Path::new(config.file.as_ref()) .file_stem() .unwrap_or_else(|| panic!("Found ill-formed path on: {}", config.file)) .to_str() .unwrap()) - }); + }).to_owned(); - let dependencies = config.dependencies.clone().unwrap_or_default(); + let dependencies = config.dependencies.clone().unwrap_or_else(|| Vec::with_capacity(0)); // TODO let partition = if config.partition.is_none() { None } else { - Some(ModulePartitionModel::from( config.partition.unwrap(),)) + Some(ModulePartitionModel::from(config.partition.as_ref().unwrap(),)) }; let file_details = utils::fs::get_file_details(&file_path).unwrap_or_else(|_| { @@ -289,9 +290,9 @@ fn assemble_module_implementation_model<'a>( let last_dot_index = config.file.as_ref().rfind('.'); if let Some(idx) = last_dot_index { let implicit_dependency = config.file.split_at(idx); - dependencies.push(Cow::Borrowed(implicit_dependency.0)) + dependencies.push(Cow::Owned(implicit_dependency.0.to_owned())) } else { - dependencies.push(Cow::Borrowed(&config.file)); + dependencies.push(config.file); } } From ffd664812f8a8f44a408f7ef8b3d6e9f072b01c0 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 23 Jun 2024 23:34:35 +0200 Subject: [PATCH 09/73] feat(wip): Finished the refactor step of the project model using Cow instead of owned data. Code compiles but is far from finished yet --- zork++/src/lib/bounds/mod.rs | 5 +- zork++/src/lib/cache/mod.rs | 68 ++++--- zork++/src/lib/cli/output/arguments.rs | 4 +- zork++/src/lib/cli/output/commands.rs | 5 +- zork++/src/lib/compiler/data_factory.rs | 18 +- zork++/src/lib/compiler/mod.rs | 56 +++--- zork++/src/lib/config_file/modules.rs | 51 +++-- zork++/src/lib/config_file/project.rs | 14 +- zork++/src/lib/lib.rs | 2 +- zork++/src/lib/project_model/compiler.rs | 38 +++- zork++/src/lib/project_model/executable.rs | 2 +- zork++/src/lib/project_model/mod.rs | 4 +- zork++/src/lib/project_model/modules.rs | 85 +++----- zork++/src/lib/project_model/project.rs | 2 +- zork++/src/lib/project_model/sourceset.rs | 43 +++-- zork++/src/lib/project_model/tests.rs | 11 +- zork++/src/lib/utils/reader.rs | 213 ++++++++++++--------- 17 files changed, 342 insertions(+), 279 deletions(-) diff --git a/zork++/src/lib/bounds/mod.rs b/zork++/src/lib/bounds/mod.rs index 90315581..0b0c212e 100644 --- a/zork++/src/lib/bounds/mod.rs +++ b/zork++/src/lib/bounds/mod.rs @@ -1,6 +1,7 @@ //! The higher abstractions of the program use core::fmt::Debug; +use std::borrow::Cow; use std::fmt::Display; use std::path::PathBuf; @@ -28,10 +29,10 @@ pub trait TranslationUnit: Display + Debug { fn path(&self) -> PathBuf; /// Outputs the declared file stem for this translation unit - fn file_stem(&self) -> String; + fn file_stem(&self) -> Cow<'_, str>; /// Outputs the declared extension for `self` - fn extension(&self) -> String; + fn extension(&self) -> Cow<'_, str>; /// Outputs the file stem concatenated with the extension for a given tu fn file_with_extension(&self) -> String { diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 319c9e76..3b235d84 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -33,7 +33,7 @@ use walkdir::WalkDir; /// Standalone utility for load from the file system the Zork++ cache file /// for the target [`CppCompiler`] -pub fn load(program_data: &ZorkModel<'_>, cli_args: &CliArgs) -> Result { +pub fn load<'a>(program_data: &'a ZorkModel<'_>, cli_args: &CliArgs) -> Result> { let compiler = program_data.compiler.cpp_compiler; let cache_path = &program_data .build @@ -88,22 +88,14 @@ pub fn save( } #[derive(Serialize, Deserialize, Default)] -pub struct ZorkCache { +pub struct ZorkCache<'a> { pub compiler: CppCompiler, pub last_program_execution: DateTime, - pub compilers_metadata: CompilersMetadata, + pub compilers_metadata: CompilersMetadata<'a>, pub generated_commands: Commands, } -impl ZorkCache { - pub fn new() -> Self { - Self { - compiler: todo!(), - last_program_execution: todo!(), - compilers_metadata: todo!(), - generated_commands: todo!(), - } - } +impl<'a> ZorkCache<'a> { pub fn last_program_execution(&self) -> &DateTime { &self.last_program_execution } @@ -150,7 +142,7 @@ impl ZorkCache { } /// The tasks associated with the cache after load it from the file system - pub fn run_tasks(&mut self, program_data: &ZorkModel<'_>) -> Result<()> { + pub fn run_tasks(&mut self, program_data: &'a ZorkModel<'_>) -> Result<()> { let compiler = program_data.compiler.cpp_compiler; if cfg!(target_os = "windows") && compiler == CppCompiler::MSVC { msvc::load_metadata(self, program_data)? @@ -186,9 +178,13 @@ impl ZorkCache { } } - if !(program_data.compiler.cpp_compiler == CppCompiler::MSVC) { + if !(program_data.compiler.cpp_compiler == CppCompiler::MSVC) + && program_data.modules.is_some() + { self.compilers_metadata.system_modules = program_data .modules + .as_ref() + .unwrap() .sys_modules .iter() .map(|e| e.to_string()) @@ -303,10 +299,10 @@ impl ZorkCache { /// to avoid recompiling them on every process /// NOTE: This feature should be deprecated and therefore, removed from Zork++ when GCC and /// Clang fully implement the required procedures to build the C++ std library as a module - fn track_system_modules<'a>( + fn track_system_modules<'b: 'a>( // TODO move it to helpers - program_data: &'a ZorkModel<'_>, - ) -> impl Iterator + 'a { + program_data: &'b ZorkModel<'b>, + ) -> impl Iterator + 'b { let root = if program_data.compiler.cpp_compiler == CppCompiler::GCC { Path::new(GCC_CACHE_DIR).to_path_buf() } else { @@ -327,11 +323,18 @@ impl ZorkCache { .expect("Error retrieving metadata") .is_file() { - program_data + program_data // TODO: review this, since it's too late and I am just satisfying the borrow checker .modules - .sys_modules + .as_ref() + .map(|modules| modules.sys_modules.clone()) + .unwrap_or_default() .iter() - .any(|sys_mod| file.file_name().to_str().unwrap().starts_with(sys_mod)) + .any(|sys_mod| { + file.file_name() + .to_str() + .unwrap() + .starts_with(&sys_mod.to_string()) + }) } else { false } @@ -390,20 +393,22 @@ pub struct MainCommandLineDetail { pub type EnvVars = HashMap; #[derive(Deserialize, Serialize, Debug, Default, Clone)] -pub struct CompilersMetadata { +pub struct CompilersMetadata<'a> { + // TODO: apply the same solution a have a fat pointer or better convert them into a Union/enum? // ALL of them must be optional, since only exists - pub msvc: MsvcMetadata, + pub msvc: MsvcMetadata<'a>, pub clang: ClangMetadata, pub gcc: GccMetadata, pub system_modules: Vec, // TODO: This hopefully will dissappear soon + // TODO: Vec of Cow } #[derive(Deserialize, Serialize, Debug, Default, Clone)] -pub struct MsvcMetadata { +pub struct MsvcMetadata<'a> { pub compiler_version: Option, pub dev_commands_prompt: Option, - pub vs_stdlib_path: Option, // std.ixx path for the MSVC std lib location - pub vs_c_stdlib_path: Option, // std.compat.ixx path for the MSVC std lib location + pub vs_stdlib_path: Option>, // std.ixx path for the MSVC std lib location + pub vs_c_stdlib_path: Option>, // std.compat.ixx path for the MSVC std lib location pub stdlib_bmi_path: PathBuf, // BMI byproduct after build in it at the target out dir of // the user pub stdlib_obj_path: PathBuf, // Same for the .obj file @@ -414,8 +419,8 @@ pub struct MsvcMetadata { pub env_vars: EnvVars, } -impl MsvcMetadata { - pub fn is_loaded(&self) -> bool { +impl<'a> MsvcMetadata<'_> { + pub fn is_loaded(&'a self) -> bool { self.dev_commands_prompt.is_some() && self.vs_stdlib_path.is_some() } } @@ -439,6 +444,7 @@ mod msvc { use crate::utils::constants; use color_eyre::eyre::{Context, OptionExt}; use regex::Regex; + use std::borrow::Cow; use std::collections::HashMap; use std::path::Path; @@ -484,13 +490,13 @@ mod msvc { Path::new(msvc.env_vars.get("VCToolsInstallDir").unwrap()).join("modules"); msvc.vs_stdlib_path = Some(SourceFile { path: vs_stdlib_path.clone(), - file_stem: String::from("std"), - extension: compiler.get_default_module_extension().to_string(), + file_stem: Cow::Borrowed("std"), + extension: compiler.default_module_extension(), }); msvc.vs_c_stdlib_path = Some(SourceFile { path: vs_stdlib_path, - file_stem: String::from("std.compat"), - extension: compiler.get_default_module_extension().to_string(), + file_stem: Cow::Borrowed("std.compat"), + extension: compiler.default_module_extension(), }); let modular_stdlib_byproducts_path = Path::new(&program_data.build.output_dir) .join(compiler.as_ref()) diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index a266c01c..3abd69fb 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -184,7 +184,7 @@ pub mod clang_args { } pub(crate) fn add_direct_module_interfaces_dependencies( - dependencies: &[&str], + dependencies: &[Cow], compiler: CppCompiler, out_dir: &Path, arguments: &mut Arguments, @@ -196,7 +196,7 @@ pub mod clang_args { .join(compiler.as_ref()) .join("modules") .join("interfaces") - .join(ifc_dep) + .join::<&str>(ifc_dep) .with_extension(compiler.get_typical_bmi_extension()) .display() ))) diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index 40936946..758138b6 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -2,6 +2,7 @@ //! by Zork++ use std::collections::HashMap; +use std::ffi::OsStr; use std::fmt::Debug; use std::slice::Iter; use std::{ @@ -131,7 +132,9 @@ fn execute_command( ) ); - std::process::Command::new(compiler.get_driver(&model.compiler)) + let driver = compiler.get_driver(&model.compiler); + let os_driver = OsStr::new(driver.as_ref()); + std::process::Command::new(os_driver) .args(arguments) .envs(cache.get_process_env_args()) .spawn()? diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index 25d386ee..f6e9120a 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -2,7 +2,10 @@ //! translation unit, having shared data without replicating it until the final command line must //! be generated in order to be stored (in cache) and executed (in the underlying shell) -use std::{borrow::Cow, path::{Path, PathBuf}}; +use std::{ + borrow::Cow, + path::{Path, PathBuf}, +}; use serde::{Deserialize, Serialize}; @@ -48,7 +51,10 @@ impl<'a> From<&'a ZorkModel<'_>> for CommonArgs { pub trait CompilerCommonArguments {} impl Default for Box { fn default() -> Self { - Box::new(ClangCommonArgs::default()) // TODO: isn't this a code smell? + Box::::default() // TODO: isn't this a code smell? + // TODO: should we just panic? Or maybe fix the default? Or maybe have an associated + // and pass the compiler to the trait fn? So we can ensure that the default has sense? + // TODO: we can just fix as well the serialization function, removing the default } } @@ -92,10 +98,10 @@ pub struct MsvcCommonArgs { exception_handling_model: Cow<'static, str>, /* no_logo: &'a str, no_compile: &'a str, // TODO: should be in the general and pass in the model? */ - // ref_stdlib: &'static str, // TODO: this are tecnically two args, /reference and the value - // ref_stdlib_compat: &'static str, // TODO: this are tecnically two args, /reference and the value - // TODO: split the dual cases per switches - // TODO: can we have switches like tuples? like switch-value pairs? + // ref_stdlib: &'static str, // TODO: this are tecnically two args, /reference and the value + // ref_stdlib_compat: &'static str, // TODO: this are tecnically two args, /reference and the value + // TODO: split the dual cases per switches + // TODO: can we have switches like tuples? like switch-value pairs? } impl MsvcCommonArgs { pub fn new() -> Self { diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index f8ae9c12..1e7e8f53 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -46,17 +46,19 @@ pub fn build_project<'a>( let mut commands = Commands::new(model, general_args, compiler_specific_common_args); // TODO from cache, and find them here instead from the cache - // Pre-tasks - if model.compiler.cpp_compiler == CppCompiler::GCC && !model.modules.sys_modules.is_empty() { - helpers::build_sys_modules(model, &mut commands, cache) - } - // TODO: add them to the commands DS, so they are together until they're generated // Build the std library as a module build_modular_stdlib(model, cache, &mut commands); // TODO: ward it with an if for only call this fn for the // 1st - Build the modules - process_modules(model, cache, &mut commands)?; + if let Some(modules) = &model.modules { + // Pre-tasks + if model.compiler.cpp_compiler == CppCompiler::GCC && !modules.sys_modules.is_empty() { + helpers::build_sys_modules(model, &mut commands, cache) + } + + process_modules(model, cache, &mut commands)?; + }; // 2nd - Build the non module sources build_sources(model, cache, &mut commands, tests)?; // 3rd - Build the executable or the tests @@ -190,10 +192,20 @@ fn process_modules( commands: &mut Commands, ) -> Result<()> { log::info!("Generating the commands for the module interfaces and partitions..."); - process_module_interfaces(model, cache, &model.modules.interfaces, commands); + process_module_interfaces( + model, + cache, + &model.modules.as_ref().unwrap().interfaces, + commands, + ); log::info!("Generating the commands for the module implementations and partitions..."); - process_module_implementations(model, cache, &model.modules.implementations, commands); + process_module_implementations( + model, + cache, + &model.modules.as_ref().unwrap().implementations, + commands, + ); Ok(()) } @@ -570,7 +582,6 @@ mod sources { let mut arguments = Arguments::default(); arguments.push(model.compiler.language_level_arg()); arguments.extend_from_slice(model.compiler.extra_args()); - arguments.extend_from_slice(model.modules.extra_args()); match compiler { CppCompiler::CLANG => { @@ -626,7 +637,7 @@ mod sources { .join(compiler.as_ref()) .join("modules") .join("implementations") - .join(implementation.file_stem()) + .join::<&str>(&*implementation.file_stem()) .with_extension(compiler.get_obj_file_extension()); commands.add_linker_file_path(&obj_file_path); @@ -646,10 +657,7 @@ mod sources { } } - let command_line = SourceCommandLine::for_translation_unit(implementation, arguments); - // commands.implementations.push(command_line); - - command_line + SourceCommandLine::for_translation_unit(implementation, arguments) } } @@ -685,15 +693,15 @@ mod helpers { let mod_unit = if compiler.eq(&CppCompiler::CLANG) { let mut temp = String::new(); if let Some(partition) = &interface.partition { - temp.push_str(partition.module); + temp.push_str(&partition.module); temp.push('-'); if !partition.partition_name.is_empty() { - temp.push_str(partition.partition_name) + temp.push_str(&partition.partition_name) } else { temp.push_str(&interface.file_stem()) } } else { - temp.push_str(interface.module_name) + temp.push_str(&interface.module_name) } temp } else { @@ -723,7 +731,7 @@ mod helpers { .join(compiler.as_ref()) .join("modules") .join("implementations") - .join(implementation.file_stem()) + .join::<&str>(&*implementation.file_stem()) .with_extension(compiler.get_obj_file_extension()) } @@ -746,21 +754,23 @@ mod helpers { let language_level = model.compiler.language_level_arg(); let sys_modules = model .modules - .sys_modules + .as_ref() + .map(|modules| modules.sys_modules.clone()) + .unwrap_or_default() .iter() .filter(|sys_module| { !cache .compilers_metadata .system_modules .iter() - .any(|s| s.eq(**sys_module)) + .any(|s| s.eq(*sys_module)) }) .map(|sys_module| { let mut v = vec![ language_level.clone(), Argument::from("-x"), Argument::from("c++-system-header"), - Argument::from(*sys_module), + Argument::from(sys_module), ]; match model.compiler.cpp_compiler { @@ -771,7 +781,7 @@ mod helpers { .join(model.compiler.cpp_compiler.as_ref()) .join("modules") .join("interfaces") - .join(sys_module) + .join(sys_module.to_string()) .with_extension( model.compiler.cpp_compiler.get_typical_bmi_extension(), ), @@ -905,7 +915,7 @@ mod helpers { out_dir .join(compiler.as_ref()) .join("sources") - .join(source.file_stem()) + .join::<&str>(&*source.file_stem()) .with_extension(compiler.get_obj_file_extension()) } } diff --git a/zork++/src/lib/config_file/modules.rs b/zork++/src/lib/config_file/modules.rs index a87f4227..d44ce81c 100644 --- a/zork++/src/lib/config_file/modules.rs +++ b/zork++/src/lib/config_file/modules.rs @@ -1,5 +1,4 @@ //!! The core section to instruct the compiler to work with C++20 modules. The most important are the base path to the interfaces and implementation files -use std::borrow::Cow; use serde::{Deserialize, Serialize}; @@ -63,18 +62,18 @@ use serde::{Deserialize, Serialize}; /// ``` #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct ModulesAttribute<'a> { - // #[serde(borrow)] - pub base_ifcs_dir: Option>, - // #[serde(borrow)] + #[serde(borrow)] + pub base_ifcs_dir: Option<&'a str>, + #[serde(borrow)] pub interfaces: Option>>, - // #[serde(borrow)] - pub base_impls_dir: Option>, - // #[serde(borrow)] + #[serde(borrow)] + pub base_impls_dir: Option<&'a str>, + #[serde(borrow)] pub implementations: Option>>, - // #[serde(borrow)] - pub sys_modules: Option>>, - // #[serde(borrow)] - pub extra_args: Option>>, + #[serde(borrow)] + pub sys_modules: Option>, + #[serde(borrow)] + pub extra_args: Option>, } /// [`ModuleInterface`] - A module interface structure for dealing @@ -137,14 +136,14 @@ pub struct ModulesAttribute<'a> { #[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(deny_unknown_fields)] pub struct ModuleInterface<'a> { - // #[serde(borrow)] - pub file: Cow<'a, str>, - // #[serde(borrow)] - pub module_name: Option>, - // #[serde(borrow)] + #[serde(borrow)] + pub file: &'a str, + #[serde(borrow)] + pub module_name: Option<&'a str>, + #[serde(borrow)] pub partition: Option>, - // #[serde(borrow)] - pub dependencies: Option>>, + #[serde(borrow)] + pub dependencies: Option>, } /// [`ModulePartition`] - Type for dealing with the parse work @@ -162,10 +161,10 @@ pub struct ModuleInterface<'a> { /// This option only takes effect with MSVC #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct ModulePartition<'a> { - // #[serde(borrow)] - pub module: Cow<'a, str>, - // #[serde(borrow)] - pub partition_name: Option>, + #[serde(borrow)] + pub module: &'a str, + #[serde(borrow)] + pub partition_name: Option<&'a str>, pub is_internal_partition: Option, } @@ -204,8 +203,8 @@ pub struct ModulePartition<'a> { /// ``` #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct ModuleImplementation<'a> { - // #[serde(borrow)] - pub file: Cow<'a, str>, - // #[serde(borrow)] - pub dependencies: Option>>, + #[serde(borrow)] + pub file: &'a str, + #[serde(borrow)] + pub dependencies: Option>, } diff --git a/zork++/src/lib/config_file/project.rs b/zork++/src/lib/config_file/project.rs index 6e7765cd..edc22c1c 100644 --- a/zork++/src/lib/config_file/project.rs +++ b/zork++/src/lib/config_file/project.rs @@ -1,7 +1,5 @@ //! Metadata about the user's project -use std::borrow::Cow; - use serde::{Deserialize, Serialize}; /// [`ProjectAttribute`] - Metadata about the user's project @@ -41,11 +39,11 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[serde(deny_unknown_fields)] pub struct ProjectAttribute<'a> { - // #[serde(borrow)] - pub name: Cow<'a, str>, - // #[serde(borrow)] - pub authors: Option>>, + #[serde(borrow)] + pub name: &'a str, + #[serde(borrow)] + pub authors: Option>, pub compilation_db: Option, - // #[serde(borrow)] - pub project_root: Option>, + #[serde(borrow)] + pub project_root: Option<&'a str>, } diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 8a1bec2b..fad3b581 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -127,7 +127,7 @@ pub mod worker { Ok(_) => autorun_generated_binary( &program_data.compiler.cpp_compiler, &program_data.build.output_dir, - program_data.executable.executable_name, + &program_data.executable.executable_name, ), Err(e) => Err(e), } diff --git a/zork++/src/lib/project_model/compiler.rs b/zork++/src/lib/project_model/compiler.rs index e31138c7..7eb9f2dd 100644 --- a/zork++/src/lib/project_model/compiler.rs +++ b/zork++/src/lib/project_model/compiler.rs @@ -17,10 +17,10 @@ pub struct CompilerModel<'a> { impl<'a> CompilerModel<'a> { pub fn language_level(&self) -> Cow<'static, str> { match self.cpp_compiler { - CppCompiler::CLANG | CppCompiler::GCC => - format!("-std=c++{}", self.cpp_standard), + CppCompiler::CLANG | CppCompiler::GCC => format!("-std=c++{}", self.cpp_standard), CppCompiler::MSVC => format!("/std:c++{}", self.cpp_standard), - }.into() + } + .into() } pub fn language_level_arg(&self) -> Argument { @@ -79,12 +79,27 @@ impl CppCompiler { } } - pub fn get_default_module_extension(&self) -> &str { - match *self { + pub fn default_module_extension<'a>(&self) -> Cow<'a, str> { + Cow::Borrowed(match *self { CppCompiler::CLANG => "cppm", CppCompiler::MSVC => "ixx", CppCompiler::GCC => "cc", - } + }) + } + pub fn get_default_module_extension<'a>(&self) -> Cow<'a, str> { + Cow::Borrowed(match *self { + CppCompiler::CLANG => "cppm", + CppCompiler::MSVC => "ixx", + CppCompiler::GCC => "cc", + }) + } + + pub fn typical_bmi_extension(&self) -> Cow<'_, str> { + Cow::Borrowed(match *self { + CppCompiler::CLANG => "pcm", + CppCompiler::MSVC => "ifc", + CppCompiler::GCC => "o", + }) } pub fn get_typical_bmi_extension(&self) -> &str { @@ -95,6 +110,14 @@ impl CppCompiler { } } + #[inline(always)] + pub fn obj_file_extension(&self) -> Cow<'_, str> { + Cow::Borrowed(match *self { + CppCompiler::CLANG | CppCompiler::GCC => "o", + CppCompiler::MSVC => "obj", + }) + } + #[inline(always)] pub fn get_obj_file_extension(&self) -> &str { match *self { @@ -135,7 +158,8 @@ impl AsRef for LanguageLevel { #[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] pub enum StdLib { STDLIBCPP, - #[default] LIBCPP, + #[default] + LIBCPP, } impl fmt::Display for StdLib { diff --git a/zork++/src/lib/project_model/executable.rs b/zork++/src/lib/project_model/executable.rs index 010b2a6c..523d9360 100644 --- a/zork++/src/lib/project_model/executable.rs +++ b/zork++/src/lib/project_model/executable.rs @@ -10,7 +10,7 @@ use super::sourceset::SourceSet; #[derive(Debug, PartialEq, Eq)] pub struct ExecutableModel<'a> { pub executable_name: Cow<'a, str>, - pub sourceset: SourceSet, + pub sourceset: SourceSet<'a>, pub extra_args: Vec, } diff --git a/zork++/src/lib/project_model/mod.rs b/zork++/src/lib/project_model/mod.rs index 34779303..add9d7ff 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -19,6 +19,6 @@ pub struct ZorkModel<'a> { pub compiler: CompilerModel<'a>, pub build: BuildModel, pub executable: ExecutableModel<'a>, - pub modules: ModulesModel<'a>, - pub tests: TestsModel, + pub modules: Option>, + pub tests: TestsModel<'a>, } diff --git a/zork++/src/lib/project_model/modules.rs b/zork++/src/lib/project_model/modules.rs index 0f68235a..55e637c2 100644 --- a/zork++/src/lib/project_model/modules.rs +++ b/zork++/src/lib/project_model/modules.rs @@ -25,8 +25,8 @@ impl<'a> ExtraArgs<'a> for ModulesModel<'a> { #[derive(Debug, PartialEq, Eq)] pub struct ModuleInterfaceModel<'a> { pub path: PathBuf, - pub file_stem: String, - pub extension: String, + pub file_stem: Cow<'a, str>, + pub extension: Cow<'a, str>, pub module_name: Cow<'a, str>, pub partition: Option>, pub dependencies: Vec>, @@ -44,42 +44,40 @@ impl<'a> fmt::Display for ModuleInterfaceModel<'a> { impl<'a> TranslationUnit for ModuleInterfaceModel<'a> { fn file(&self) -> PathBuf { - let mut tmp = self.path.join(&self.file_stem).into_os_string(); - tmp.push("."); - tmp.push(&self.extension); - PathBuf::from(tmp) + self.path + .join::<&str>(&self.file_stem) + .join::<&str>(&self.extension) } fn path(&self) -> PathBuf { self.path.clone() } - fn file_stem(&self) -> String { + fn file_stem(&self) -> Cow<'_, str> { self.file_stem.clone() } - fn extension(&self) -> String { + fn extension(&self) -> Cow<'_, str> { self.extension.clone() } } impl<'a> TranslationUnit for &'a ModuleInterfaceModel<'a> { fn file(&self) -> PathBuf { - let mut tmp = self.path.join(&self.file_stem).into_os_string(); - tmp.push("."); - tmp.push(&self.extension); - PathBuf::from(tmp) + self.path + .join::<&str>(&self.file_stem) + .join::<&str>(&self.extension) } fn path(&self) -> PathBuf { self.path.clone() } - fn file_stem(&self) -> String { + fn file_stem(&self) -> Cow<'_, str> { self.file_stem.clone() } - fn extension(&self) -> String { + fn extension(&self) -> Cow<'a, str> { self.extension.clone() } } @@ -91,32 +89,31 @@ pub struct ModulePartitionModel<'a> { pub is_internal_partition: bool, } - impl<'a> From> for ModulePartitionModel<'a> { fn from(value: ModulePartition<'a>) -> Self { Self { - module: value.module, - partition_name: value.partition_name.unwrap_or_default(), + module: Cow::Borrowed(value.module), + partition_name: Cow::Borrowed(value.partition_name.unwrap_or_default()), is_internal_partition: value.is_internal_partition.unwrap_or_default(), } } } -impl<'a> From<&ModulePartition<'a>> for ModulePartitionModel<'a> { - fn from(value: &ModulePartition<'a>) -> &'a Self { - Self { - module: value.module., - partition_name: value.partition_name.unwrap_or_default(), - is_internal_partition: value.is_internal_partition.unwrap_or_default(), - } - } -} +// impl<'a> From<&ModulePartition<'a>> for ModulePartitionModel<'a> { +// fn from(value: &ModulePartition<'a>) -> &'a Self { +// Self { +// module: value.module., +// partition_name: value.partition_name.unwrap_or_default(), +// is_internal_partition: value.is_internal_partition.unwrap_or_default(), +// } +// } +// } #[derive(Debug, PartialEq, Eq)] pub struct ModuleImplementationModel<'a> { pub path: PathBuf, - pub file_stem: String, - pub extension: String, + pub file_stem: Cow<'a, str>, + pub extension: Cow<'a, str>, pub dependencies: Vec>, } @@ -126,44 +123,22 @@ impl<'a> fmt::Display for ModuleImplementationModel<'a> { } } -impl<'a> TranslationUnit for ModuleImplementationModel<'a> { - fn file(&self) -> PathBuf { - let mut tmp = self.path.join(&self.file_stem).into_os_string(); - tmp.push("."); - tmp.push(&self.extension); - PathBuf::from(tmp) - } - - fn path(&self) -> PathBuf { - self.path.clone() - } - - fn file_stem(&self) -> String { - self.file_stem.clone() - } - - fn extension(&self) -> String { - self.extension.clone() - } -} - impl<'a> TranslationUnit for &'a ModuleImplementationModel<'a> { fn file(&self) -> PathBuf { - let mut tmp = self.path.join(&self.file_stem).into_os_string(); - tmp.push("."); - tmp.push(&self.extension); - PathBuf::from(tmp) + self.path + .join::<&str>(&self.file_stem) + .with_extension::<&str>(&self.extension) } fn path(&self) -> PathBuf { self.path.clone() } - fn file_stem(&self) -> String { + fn file_stem(&self) -> Cow<'_, str> { self.file_stem.clone() } - fn extension(&self) -> String { + fn extension(&self) -> Cow<'_, str> { self.extension.clone() } } diff --git a/zork++/src/lib/project_model/project.rs b/zork++/src/lib/project_model/project.rs index 7c8a3c7a..e7905cfa 100644 --- a/zork++/src/lib/project_model/project.rs +++ b/zork++/src/lib/project_model/project.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; #[derive(Debug, PartialEq, Eq)] pub struct ProjectModel<'a> { pub name: Cow<'a, str>, - pub authors: &'a [Cow<'a, str>], + pub authors: Vec>, pub compilation_db: bool, pub project_root: Option>, } diff --git a/zork++/src/lib/project_model/sourceset.rs b/zork++/src/lib/project_model/sourceset.rs index dfb55000..55e232d2 100644 --- a/zork++/src/lib/project_model/sourceset.rs +++ b/zork++/src/lib/project_model/sourceset.rs @@ -1,4 +1,5 @@ use core::fmt; +use std::borrow::Cow; use std::path::{Path, PathBuf}; use crate::bounds::TranslationUnit; @@ -26,56 +27,56 @@ impl File for PathBuf { } } +// TODO: All the trait File impl as well as the trait aren't required anymore + #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] -pub struct SourceFile { +pub struct SourceFile<'a> { pub path: PathBuf, - pub file_stem: String, - pub extension: String, + pub file_stem: Cow<'a, str>, + pub extension: Cow<'a, str>, } -impl TranslationUnit for SourceFile { +impl<'a> TranslationUnit for SourceFile<'a> { fn file(&self) -> PathBuf { - let mut tmp = self.path.join(&self.file_stem).into_os_string(); - tmp.push("."); // TODO: use the correct PATH APIs - tmp.push(&self.extension); - PathBuf::from(tmp) + self.path + .join::<&str>(&self.file_stem) + .with_extension::<&str>(&self.extension) } fn path(&self) -> PathBuf { self.path.clone() } - fn file_stem(&self) -> String { + fn file_stem(&self) -> Cow<'_, str> { self.file_stem.clone() } - fn extension(&self) -> String { + fn extension(&self) -> Cow<'_, str> { self.extension.clone() } } -impl TranslationUnit for &SourceFile { +impl<'a> TranslationUnit for &'a SourceFile<'a> { fn file(&self) -> PathBuf { - let mut tmp = self.path.join(&self.file_stem).into_os_string(); - tmp.push("."); - tmp.push(&self.extension); - PathBuf::from(tmp) + self.path + .join::<&str>(&self.file_stem) + .with_extension::<&str>(&self.extension) } fn path(&self) -> PathBuf { self.path.clone() } - fn file_stem(&self) -> String { + fn file_stem(&self) -> Cow<'_, str> { self.file_stem.clone() } - fn extension(&self) -> String { + fn extension(&self) -> Cow<'_, str> { self.extension.clone() } } -impl fmt::Display for SourceFile { +impl<'a> fmt::Display for SourceFile<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -114,11 +115,11 @@ impl GlobPattern { } #[derive(Debug, PartialEq, Eq)] -pub struct SourceSet { - pub sources: Vec, +pub struct SourceSet<'a> { + pub sources: Vec>, } -impl SourceSet { +impl<'a> SourceSet<'a> { pub fn as_args_to(&self, dst: &mut Vec) -> Result<()> { let args = self.sources.iter().map(|sf| sf.file()).map(Argument::from); diff --git a/zork++/src/lib/project_model/tests.rs b/zork++/src/lib/project_model/tests.rs index e8dd7afb..0261991b 100644 --- a/zork++/src/lib/project_model/tests.rs +++ b/zork++/src/lib/project_model/tests.rs @@ -2,23 +2,24 @@ use crate::{ bounds::{ExecutableTarget, ExtraArgs}, cli::output::arguments::Argument, }; +use std::borrow::Cow; use super::sourceset::SourceSet; #[derive(Debug, PartialEq, Eq)] -pub struct TestsModel { - pub test_executable_name: String, - pub sourceset: SourceSet, +pub struct TestsModel<'a> { + pub test_executable_name: Cow<'a, str>, + pub sourceset: SourceSet<'a>, pub extra_args: Vec, } -impl<'a> ExtraArgs<'a> for TestsModel { +impl<'a> ExtraArgs<'a> for TestsModel<'a> { fn extra_args(&'a self) -> &'a [Argument] { &self.extra_args } } -impl<'a> ExecutableTarget<'a> for TestsModel { +impl<'a> ExecutableTarget<'a> for TestsModel<'a> { fn name(&'a self) -> &'a str { &self.test_executable_name } diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 5a7dd66c..b773112b 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -95,14 +95,22 @@ pub fn build_model<'a>( cli_args: &'a CliArgs, absolute_project_root: &Path, ) -> Result> { - let proj_name = config.project.name.clone(); + let proj_name = config.project.name; let project = assemble_project_model(config.project); let compiler = assemble_compiler_model(config.compiler, cli_args); let build = assemble_build_model(config.build, absolute_project_root); - let executable = assemble_executable_model(proj_name, config.executable, absolute_project_root); + let executable = assemble_executable_model( + Cow::Borrowed(proj_name), + config.executable, + absolute_project_root, + ); let modules = assemble_modules_model(config.modules, absolute_project_root); - let tests = assemble_tests_model(project.name.as_ref(), config.tests, absolute_project_root); + let tests = assemble_tests_model( + Cow::Borrowed(proj_name), + config.tests, + absolute_project_root, + ); Ok(ZorkModel { project, @@ -114,26 +122,27 @@ pub fn build_model<'a>( }) } -fn assemble_project_model<'a>(config: ProjectAttribute<'a>) -> ProjectModel<'a> { +fn assemble_project_model(config: ProjectAttribute) -> ProjectModel { ProjectModel { - name: config.name, + name: Cow::Borrowed(config.name), authors: config .authors - // .as_ref() - // .map_or_else(|| &[] as &[Cow<'a, str>], |auths| auths.as_slice()), - .map_or_else(|| &[] as [Cow<'a, str>], |auths| &*auths), + .as_ref() + .map_or_else(Vec::default, |authors| { + authors + .iter() + .map(|auth| Cow::Borrowed(*auth)) + .collect::>() + }), compilation_db: config.compilation_db.unwrap_or_default(), - project_root: config.project_root, + project_root: config.project_root.map(Cow::Borrowed), } } -fn assemble_compiler_model<'a>( - config: CompilerAttribute, - cli_args: &'a CliArgs, -) -> CompilerModel<'a> { +fn assemble_compiler_model(config: CompilerAttribute, cli_args: &CliArgs) -> CompilerModel { let extra_args = config .extra_args - .map(|args| args.into_iter().map(|arg| Argument::from(arg)).collect()) + .map(|args| args.into_iter().map(Argument::from).collect()) .unwrap_or_default(); CompilerModel { @@ -163,15 +172,15 @@ fn assemble_build_model(config: Option, project_root: &Path) -> //noinspection ALL fn assemble_executable_model<'a>( - project_name: &Cow<'a, str>, + project_name: Cow<'a, str>, config: Option>, project_root: &Path, ) -> ExecutableModel<'a> { let config = config.as_ref(); let executable_name = config - .and_then(|exe| -> Option> {exe.executable_name.clone()}) - .unwrap_or_else(|| project_name.clone()); + .and_then(|exe| -> Option> { exe.executable_name.clone() }) + .unwrap_or(project_name); let sources = config .and_then(|exe| exe.sources.clone()) @@ -181,7 +190,7 @@ fn assemble_executable_model<'a>( let extra_args = config .and_then(|exe| exe.extra_args.as_ref()) - .map(|args| args.iter().map(|arg| Argument::from(arg)).collect()) + .map(|args| args.iter().map(Argument::from).collect()) .unwrap_or_default(); ExecutableModel { @@ -194,84 +203,107 @@ fn assemble_executable_model<'a>( fn assemble_modules_model<'a>( config: Option>, project_root: &Path, -) -> ModulesModel<'a> { - let base_ifcs_dir = config - .as_ref() - .and_then(|modules| modules.base_ifcs_dir) - .unwrap_or(Cow::Borrowed(".")); +) -> Option> { + config.as_ref()?; // early guard + let modules = config.unwrap(); + + let base_ifcs_dir = modules + .base_ifcs_dir + .map(Path::new) + .unwrap_or_else(|| Path::new(".")); - let interfaces = config - .and_then(|modules| modules.interfaces.as_ref()) + let interfaces = modules + .interfaces .map(|ifcs| { - ifcs.iter() - .map(|m_ifc| -> ModuleInterfaceModel<'_> {assemble_module_interface_model(m_ifc, base_ifcs_dir, project_root)}) + ifcs.into_iter() + .map(|m_ifc| -> ModuleInterfaceModel<'_> { + assemble_module_interface_model(m_ifc, base_ifcs_dir, project_root) + }) .collect() }) .unwrap_or_default(); - let base_impls_dir = config - .and_then(|modules| modules.base_impls_dir) - .unwrap_or(Cow::Borrowed(".")); + let base_impls_dir = modules + .base_impls_dir + .map(Path::new) + .unwrap_or_else(|| Path::new(".")); - let implementations = config - .and_then(|modules| modules.implementations.as_ref()) + let implementations = modules + .implementations .map(|impls| { impls - .iter() + .into_iter() .map(|m_impl| { - assemble_module_implementation_model(m_impl, &base_impls_dir, project_root) + assemble_module_implementation_model(m_impl, base_impls_dir, project_root) }) .collect() }) .unwrap_or_default(); - let sys_modules = config - .and_then(|modules| modules.sys_modules.as_ref()) - .map_or_else(Default::default, |headers| headers.clone()); + let sys_modules = modules + .sys_modules + .as_ref() + .map_or_else(Default::default, |headers| { + headers + .iter() + .map(|sys_header| Cow::from(*sys_header)) + .collect() + }); - let extra_args = config - .and_then(|mod_attr| mod_attr.extra_args.as_ref()) + let extra_args = modules // TODO: this has to dissappear from the Zork++ build options + .extra_args + .as_ref() .map(|args| args.iter().map(|arg| Argument::from(*arg)).collect()) .unwrap_or_default(); - ModulesModel { - base_ifcs_dir: Path::new(&base_ifcs_dir), + Some(ModulesModel { + base_ifcs_dir, interfaces, - base_impls_dir: Path::new(&base_impls_dir), + base_impls_dir, implementations, sys_modules, extra_args, - } + }) } fn assemble_module_interface_model<'a>( - config: &'a ModuleInterface, - base_path: &str, + config: ModuleInterface<'a>, + base_path: &Path, project_root: &Path, ) -> ModuleInterfaceModel<'a> { - let file_path = Path::new(project_root).join(base_path).join(config.file.as_ref()); - let module_name = config.module_name.clone().unwrap_or_else(|| { - Cow::Borrowed(Path::new(config.file.as_ref()) - .file_stem() - .unwrap_or_else(|| panic!("Found ill-formed path on: {}", config.file)) - .to_str() - .unwrap()) - }).to_owned(); - - let dependencies = config.dependencies.clone().unwrap_or_else(|| Vec::with_capacity(0)); // TODO + let cfg_file = config.file; + + let file_path = Path::new(project_root).join(base_path).join(cfg_file); + // let module_name = config.module_name.unwrap_or_else(move || { + // Path::new(&cfg_file) + // .file_stem() + // .unwrap_or_else(|| panic!("Found ill-formed path on: {cfg_file}")) + // .to_string_lossy() + // } // TODO: ensure that this one is valid with modules that contains dots in their name + let module_name = Cow::Borrowed( + config + .module_name + .unwrap_or_else(|| panic!("Found ill-formed module name on: {cfg_file}")), + ); + + let dependencies = config + .dependencies + .map(|deps| deps.into_iter().map(Cow::Borrowed).collect()) + .unwrap_or_default(); let partition = if config.partition.is_none() { None } else { - Some(ModulePartitionModel::from(config.partition.as_ref().unwrap(),)) + Some(ModulePartitionModel::from(config.partition.unwrap())) }; let file_details = utils::fs::get_file_details(&file_path).unwrap_or_else(|_| { panic!("An unexpected error happened getting the file details for {file_path:?}") }); + ModuleInterfaceModel { path: file_details.0, - file_stem: file_details.1, - extension: file_details.2, + file_stem: Cow::from(file_details.1), + extension: Cow::from(file_details.2), module_name, partition, dependencies, @@ -280,19 +312,25 @@ fn assemble_module_interface_model<'a>( fn assemble_module_implementation_model<'a>( config: ModuleImplementation<'a>, - base_path: &str, + base_path: &Path, project_root: &Path, ) -> ModuleImplementationModel<'a> { - let mut dependencies = config.dependencies.unwrap_or_default(); + let mut dependencies = config + .dependencies + .unwrap_or_default() + .into_iter() + .map(Cow::Borrowed) + .collect::>>(); - let file_path = Path::new(project_root).join(base_path).join(config.file.as_ref()); + let file_path = Path::new(project_root).join(base_path).join(config.file); if dependencies.is_empty() { - let last_dot_index = config.file.as_ref().rfind('.'); + // TODO: can't recall what's this, so please, debug it and document it + let last_dot_index = config.file.rfind('.'); if let Some(idx) = last_dot_index { let implicit_dependency = config.file.split_at(idx); dependencies.push(Cow::Owned(implicit_dependency.0.to_owned())) } else { - dependencies.push(config.file); + dependencies.push(Cow::Borrowed(config.file)); } } @@ -302,17 +340,17 @@ fn assemble_module_implementation_model<'a>( ModuleImplementationModel { path: file_details.0, - file_stem: file_details.1, - extension: file_details.2, + file_stem: Cow::Owned(file_details.1), + extension: Cow::Owned(file_details.2), dependencies, } } fn assemble_tests_model<'a>( - project_name: &'a str, + project_name: Cow<'_, str>, config: Option, project_root: &Path, -) -> TestsModel { +) -> TestsModel<'a> { let config = config.as_ref(); let test_executable_name = config.and_then(|exe| exe.test_executable_name).map_or_else( @@ -321,7 +359,8 @@ fn assemble_tests_model<'a>( ); let sources = config - .and_then(|exe| exe.sources.clone()) + .and_then(|exe| exe.sources.as_ref()) + .map(|srcs| srcs.iter().map(|src| Cow::Borrowed(*src)).collect()) .unwrap_or_default(); let sourceset = get_sourceset_for(sources, project_root); @@ -331,13 +370,13 @@ fn assemble_tests_model<'a>( .unwrap_or_default(); TestsModel { - test_executable_name, + test_executable_name: Cow::Owned(test_executable_name), sourceset, extra_args, } } -fn get_sourceset_for(srcs: Vec>, project_root: &Path) -> SourceSet { +fn get_sourceset_for<'a>(srcs: Vec>, project_root: &Path) -> SourceSet<'a> { let sources = srcs .iter() .map(|src| { @@ -359,8 +398,8 @@ fn get_sourceset_for(srcs: Vec>, project_root: &Path) -> SourceSet }); SourceFile { path: file_details.0, - file_stem: file_details.1, - extension: file_details.2, + file_stem: Cow::Owned(file_details.1), + extension: Cow::Owned(file_details.2), } }) .collect(); @@ -402,7 +441,7 @@ mod test { let expected = ZorkModel { project: ProjectModel { name: "Zork++".into(), - authors: &["zerodaycode.gz@gmail.com".into()], + authors: vec!["zerodaycode.gz@gmail.com".into()], compilation_db: false, project_root: None, }, @@ -421,14 +460,14 @@ mod test { sourceset: SourceSet { sources: vec![] }, extra_args: vec![], }, - modules: ModulesModel { + modules: Some(ModulesModel { base_ifcs_dir: Path::new("."), interfaces: vec![], base_impls_dir: Path::new("."), implementations: vec![], sys_modules: vec![], extra_args: vec![], - }, + }), tests: TestsModel { test_executable_name: "Zork++_test".into(), sourceset: SourceSet { sources: vec![] }, @@ -452,7 +491,7 @@ mod test { let expected = ZorkModel { project: ProjectModel { name: "Zork++".into(), - authors: &["zerodaycode.gz@gmail.com".into()], + authors: vec!["zerodaycode.gz@gmail.com".into()], compilation_db: true, project_root: None, }, @@ -471,21 +510,21 @@ mod test { sourceset: SourceSet { sources: vec![] }, extra_args: vec![Argument::from("-Werr")], }, - modules: ModulesModel { + modules: Some(ModulesModel { base_ifcs_dir: Path::new("ifcs"), interfaces: vec![ ModuleInterfaceModel { path: abs_path_for_mock.join("ifcs"), - file_stem: String::from("maths"), - extension: String::from("cppm"), + file_stem: Cow::Borrowed("maths"), + extension: Cow::Borrowed("cppm"), module_name: "maths".into(), partition: None, dependencies: vec![], }, ModuleInterfaceModel { path: abs_path_for_mock.join("ifcs"), - file_stem: String::from("some_module"), - extension: String::from("cppm"), + file_stem: Cow::Borrowed("some_module"), + extension: Cow::Borrowed("cppm"), module_name: "maths".into(), partition: None, dependencies: vec![], @@ -495,22 +534,22 @@ mod test { implementations: vec![ ModuleImplementationModel { path: abs_path_for_mock.join("srcs"), - file_stem: String::from("maths"), - extension: String::from("cpp"), + file_stem: Cow::from("maths"), + extension: Cow::from("cpp"), dependencies: vec!["maths".into()], }, ModuleImplementationModel { path: abs_path_for_mock.join("srcs"), - file_stem: String::from("some_module_impl"), - extension: String::from("cpp"), + file_stem: Cow::from("some_module_impl"), + extension: Cow::from("cpp"), dependencies: vec!["iostream".into()], }, ], sys_modules: vec!["iostream".into()], extra_args: vec![Argument::from("-Wall")], - }, + }), tests: TestsModel { - test_executable_name: "zork_check".to_string(), + test_executable_name: "zork_check".into(), sourceset: SourceSet { sources: vec![] }, extra_args: vec![Argument::from("-pedantic")], }, From f7c928c39f886648136133c926b2338c83e0c15a Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 24 Jun 2024 19:49:39 +0200 Subject: [PATCH 10/73] feat(system_headers): Avoiding check the fs for system headers to see if they are already built if there's no header declared by the user. This closes #116 --- zork++/src/lib/cache/mod.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 3b235d84..f0e99812 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -12,6 +12,7 @@ use std::{ fs::File, path::{Path, PathBuf}, }; +use std::borrow::Cow; use crate::bounds::TranslationUnit; use crate::cache::compile_commands::CompileCommands; @@ -148,7 +149,8 @@ impl<'a> ZorkCache<'a> { msvc::load_metadata(self, program_data)? } - if compiler != CppCompiler::MSVC { + if compiler != CppCompiler::MSVC && helpers::user_declared_system_headers_to_build(program_data) + { let i = Self::track_system_modules(program_data); self.compilers_metadata.system_modules.clear(); self.compilers_metadata.system_modules.extend(i); @@ -543,3 +545,16 @@ mod msvc { Ok(env_vars) } } + +mod helpers { + use std::borrow::Cow; + use crate::project_model::ZorkModel; + + pub(crate) fn user_declared_system_headers_to_build(program_data: &ZorkModel<'_>) -> bool { + program_data + .modules + .as_ref() + .map(|mods| mods.sys_modules.as_ref()) + .is_some_and(|sys_modules: &Vec>| !sys_modules.is_empty()) + } +} From 48a95ee77873ca9b714f2ca4799bee04bd9cc6fe Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 24 Jun 2024 21:28:47 +0200 Subject: [PATCH 11/73] fix: missed .with_extension methods on the implementations of TranslationUnit fix: we can't use join for file_stem(s) that contains dots in their names, since it skips anything after the first point --- zork++/src/lib/bounds/mod.rs | 25 +++++++++++++ zork++/src/lib/cache/mod.rs | 1 - zork++/src/lib/compiler/mod.rs | 45 ++++++++++++++--------- zork++/src/lib/project_model/modules.rs | 15 +++----- zork++/src/lib/project_model/sourceset.rs | 10 ++--- zork++/src/lib/utils/reader.rs | 26 +++++-------- 6 files changed, 72 insertions(+), 50 deletions(-) diff --git a/zork++/src/lib/bounds/mod.rs b/zork++/src/lib/bounds/mod.rs index 0b0c212e..d97d30c6 100644 --- a/zork++/src/lib/bounds/mod.rs +++ b/zork++/src/lib/bounds/mod.rs @@ -23,6 +23,31 @@ pub trait ExecutableTarget<'a>: ExtraArgs<'a> { pub trait TranslationUnit: Display + Debug { /// Returns the file, being the addition of the path property plus the file stem plus /// the extension property + /// + /// # Examples + /// + /// ``` + /// use std::borrow::Cow; + /// use std::path::PathBuf; + /// use zork::bounds::TranslationUnit; + /// use zork::project_model::sourceset::SourceFile; + /// + /// let source_file = SourceFile { + /// path: PathBuf::from("/usr/include"), + /// file_stem: Cow::from("std"), + /// extension: Cow::from("h"), + /// }; + /// + /// assert_eq!(source_file.file(), PathBuf::from("/usr/include/std.h")); + /// + /// let source_file_compat = SourceFile { + /// path: PathBuf::from("/usr/include"), + /// file_stem: Cow::from("std.compat"), + /// extension: Cow::from("h"), + /// }; + /// + /// assert_eq!(source_file_compat.file(), PathBuf::from("/usr/include/std.compat.h")); + /// ``` fn file(&self) -> PathBuf; /// Outputs the declared path for `self`, being self the translation unit diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index f0e99812..f6665f65 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -12,7 +12,6 @@ use std::{ fs::File, path::{Path, PathBuf}, }; -use std::borrow::Cow; use crate::bounds::TranslationUnit; use crate::cache::compile_commands::CompileCommands; diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 1e7e8f53..d9b007d0 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -84,6 +84,7 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: let compiler = model.compiler.cpp_compiler; // TODO: remaining ones: Clang, GCC + // TODO: try to abstract the procedures into just one entity if compiler.eq(&CppCompiler::MSVC) { let built_stdlib_path = &cache.compilers_metadata.msvc.stdlib_bmi_path; let cpp_stdlib = if !built_stdlib_path.exists() { @@ -93,8 +94,6 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: ); msvc_args::generate_std_cmd(model, cache, StdLibMode::Cpp) // TODO move mod msvc_args to commands } else { - // TODO: p.ej: existe y además tiene status cached? modificar por &mut - // TODO: no será mejor sacarla de la caché? let source_command_line = SourceCommandLine { directory: built_stdlib_path.file_stem().unwrap().into(), filename: built_stdlib_path @@ -108,10 +107,11 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: }; source_command_line }; + log::info!("Generated std SourceCommandLine: {cpp_stdlib:?}"); commands.pre_tasks.push(cpp_stdlib); let built_stdlib_compat_path = &cache.compilers_metadata.msvc.c_stdlib_bmi_path; - let c_cpp_stdlib = if !built_stdlib_path.exists() { + let c_compat = if !built_stdlib_path.exists() { log::trace!("Building the {:?} C compat CPP std lib", compiler); msvc_args::generate_std_cmd(model, cache, StdLibMode::CCompat) } else { @@ -128,7 +128,8 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: }; source_command_line }; - commands.pre_tasks.push(c_cpp_stdlib); + log::info!("Generated std SourceCommandLine: {c_compat:?}"); + commands.pre_tasks.push(c_compat); } } @@ -168,13 +169,13 @@ fn build_sources( sources::generate_sources_arguments(model, commands, cache, &model.tests, src); } else { let command_line = SourceCommandLine::from_translation_unit( - src, Arguments::default(), true, CommandExecutionResult::Cached + src, Arguments::default(), true, CommandExecutionResult::Cached, ); log::trace!("Source file: {:?} was not modified since the last iteration. No need to rebuilt it again.", &src.file()); commands.sources.push(command_line); commands.add_linker_file_path_owned(helpers::generate_obj_file_path( - model.compiler.cpp_compiler, &model.build.output_dir, src + model.compiler.cpp_compiler, &model.build.output_dir, src, )) }); @@ -225,12 +226,13 @@ fn process_module_interfaces<'a>( let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, lpe, generated_cmd, &module_interface.file()); log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_interface.file()); - if !translation_unit_must_be_rebuilt { log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &module_interface.file()); + if !translation_unit_must_be_rebuilt { + log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &module_interface.file()); } let mut cached_cmd_line = generated_cmd.clone(); // TODO: somehow, we should manage to solve this on the future cached_cmd_line.need_to_build = translation_unit_must_be_rebuilt; commands.linker.add_owned_buildable_at(helpers::generate_prebuilt_miu( // TODO: extremely provisional - model.compiler.cpp_compiler, &model.build.output_dir, module_interface + model.compiler.cpp_compiler, &model.build.output_dir, module_interface, )); cached_cmd_line } else { @@ -265,7 +267,7 @@ fn process_module_implementations<'a>( let mut cached_cmd_line = generated_cmd.clone(); // TODO: somehow, we should manage to solve this on the future cached_cmd_line.need_to_build = translation_unit_must_be_rebuilt; commands.linker.add_owned_buildable_at(helpers::generate_impl_obj_file( // TODO: extremely provisional - model.compiler.cpp_compiler, &model.build.output_dir, module_impl + model.compiler.cpp_compiler, &model.build.output_dir, module_impl, )); cached_cmd_line } else { @@ -479,7 +481,9 @@ mod sources { let compiler = model.compiler.cpp_compiler; let out_dir: &Path = model.build.output_dir.as_ref(); - let mut arguments = Arguments::default(); + let mut arguments = Arguments::default(); // TODO: provisional while we're implementing the Flyweights + arguments.push(model.compiler.language_level_arg()); + arguments.extend_from_slice(model.compiler.extra_args()); match compiler { CppCompiler::CLANG => { @@ -637,7 +641,7 @@ mod sources { .join(compiler.as_ref()) .join("modules") .join("implementations") - .join::<&str>(&*implementation.file_stem()) + .join::<&str>(&implementation.file_stem()) .with_extension(compiler.get_obj_file_extension()); commands.add_linker_file_path(&obj_file_path); @@ -868,11 +872,13 @@ mod helpers { } /// TODO - pub(crate) fn translation_unit_must_be_rebuilt( - compiler: CppCompiler, - last_process_execution: &DateTime, - cached_source_cmd: &SourceCommandLine, - file: &Path, + pub(crate) fn translation_unit_must_be_rebuilt( // TODO: separation of concerns? Please + // Just make two fns, the one that checks for the status and the one that checks for modifications + // then just use a template-factory design pattern by just abstracting away the two checks in one call + compiler: CppCompiler, + last_process_execution: &DateTime, + cached_source_cmd: &SourceCommandLine, + file: &Path, ) -> bool { if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { log::trace!("Module unit {:?} will be rebuilt since we've detected that you are using Clang in Windows", cached_source_cmd.path()); @@ -882,9 +888,14 @@ mod helpers { let execution_result = cached_source_cmd.execution_result; if execution_result != CommandExecutionResult::Success && execution_result != CommandExecutionResult::Cached + // TODO: Big one. What instead of having the boolean flag of need_to_built we modify the enumerated of execution result, change its name + // to TranslationUnitStatus or some other better name pls, and we introduce a variant for mark the files that must be built? + // TODO: also, introduce a variant like PendingToBuilt, that just will be check before executing the commands? + // TODO: and even better, what if we use the new enum type as a wrapper over execution result? So we can store inside the variants + // that contains build info the execution result, and in the others maybe nothing, or other interesting data? { log::trace!( - "File {file:?} build process failed previously with status: {:?}. It will be rebuilt again", + "File {file:?} build process failed previously with status: {:?}. It will be rebuilt again", // TODO: technically, it can be Unprocessed, which isn't a failure execution_result ); return true; diff --git a/zork++/src/lib/project_model/modules.rs b/zork++/src/lib/project_model/modules.rs index 55e637c2..203ef2f0 100644 --- a/zork++/src/lib/project_model/modules.rs +++ b/zork++/src/lib/project_model/modules.rs @@ -44,9 +44,8 @@ impl<'a> fmt::Display for ModuleInterfaceModel<'a> { impl<'a> TranslationUnit for ModuleInterfaceModel<'a> { fn file(&self) -> PathBuf { - self.path - .join::<&str>(&self.file_stem) - .join::<&str>(&self.extension) + let file_name = format!("{}.{}", self.file_stem, self.extension); + self.path().join(file_name) } fn path(&self) -> PathBuf { @@ -64,9 +63,8 @@ impl<'a> TranslationUnit for ModuleInterfaceModel<'a> { impl<'a> TranslationUnit for &'a ModuleInterfaceModel<'a> { fn file(&self) -> PathBuf { - self.path - .join::<&str>(&self.file_stem) - .join::<&str>(&self.extension) + let file_name = format!("{}.{}", self.file_stem, self.extension); + self.path().join(file_name) } fn path(&self) -> PathBuf { @@ -125,9 +123,8 @@ impl<'a> fmt::Display for ModuleImplementationModel<'a> { impl<'a> TranslationUnit for &'a ModuleImplementationModel<'a> { fn file(&self) -> PathBuf { - self.path - .join::<&str>(&self.file_stem) - .with_extension::<&str>(&self.extension) + let file_name = format!("{}.{}", self.file_stem, self.extension); + self.path().join(file_name) } fn path(&self) -> PathBuf { diff --git a/zork++/src/lib/project_model/sourceset.rs b/zork++/src/lib/project_model/sourceset.rs index 55e232d2..6761be4d 100644 --- a/zork++/src/lib/project_model/sourceset.rs +++ b/zork++/src/lib/project_model/sourceset.rs @@ -38,9 +38,8 @@ pub struct SourceFile<'a> { impl<'a> TranslationUnit for SourceFile<'a> { fn file(&self) -> PathBuf { - self.path - .join::<&str>(&self.file_stem) - .with_extension::<&str>(&self.extension) + let file_name = format!("{}.{}", self.file_stem, self.extension); + self.path().join(file_name) } fn path(&self) -> PathBuf { @@ -58,9 +57,8 @@ impl<'a> TranslationUnit for SourceFile<'a> { impl<'a> TranslationUnit for &'a SourceFile<'a> { fn file(&self) -> PathBuf { - self.path - .join::<&str>(&self.file_stem) - .with_extension::<&str>(&self.extension) + let file_name = format!("{}.{}", self.file_stem, self.extension); + self.path().join(file_name) } fn path(&self) -> PathBuf { diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index b773112b..eee4e7b8 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -274,27 +274,19 @@ fn assemble_module_interface_model<'a>( let cfg_file = config.file; let file_path = Path::new(project_root).join(base_path).join(cfg_file); - // let module_name = config.module_name.unwrap_or_else(move || { - // Path::new(&cfg_file) - // .file_stem() - // .unwrap_or_else(|| panic!("Found ill-formed path on: {cfg_file}")) - // .to_string_lossy() - // } // TODO: ensure that this one is valid with modules that contains dots in their name - let module_name = Cow::Borrowed( - config - .module_name - .unwrap_or_else(|| panic!("Found ill-formed module name on: {cfg_file}")), - ); - + let module_name = if let Some(mod_name) = config.module_name { + Cow::Borrowed(mod_name) + } else { + Path::new(cfg_file) + .file_stem() + .unwrap_or_else(|| panic!("Found ill-formed file_stem data for: {cfg_file}")) + .to_string_lossy() + }; let dependencies = config .dependencies .map(|deps| deps.into_iter().map(Cow::Borrowed).collect()) .unwrap_or_default(); - let partition = if config.partition.is_none() { - None - } else { - Some(ModulePartitionModel::from(config.partition.unwrap())) - }; + let partition = config.partition.map(ModulePartitionModel::from); let file_details = utils::fs::get_file_details(&file_path).unwrap_or_else(|_| { panic!("An unexpected error happened getting the file details for {file_path:?}") From 7535b3a5f70660485507dd2859a5ed50761394ae Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 24 Jun 2024 21:32:33 +0200 Subject: [PATCH 12/73] fix: removed unused borrows of the project configuration since its ownership is transferred to the build_model procedure --- zork++/benches/benchmarks.rs | 2 +- zork++/src/lib/cache/mod.rs | 5 +++-- zork++/src/lib/compiler/mod.rs | 15 ++++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/zork++/benches/benchmarks.rs b/zork++/benches/benchmarks.rs index 54305ec5..7e8af8e3 100644 --- a/zork++/benches/benchmarks.rs +++ b/zork++/benches/benchmarks.rs @@ -16,7 +16,7 @@ pub fn build_project_benchmark(c: &mut Criterion) { let config: ZorkConfigFile = config_file::zork_cfg_from_file(utils::constants::CONFIG_FILE_MOCK).unwrap(); let cli_args = CliArgs::parse(); - let program_data = build_model(&config, &cli_args, Path::new(".")).unwrap(); + let program_data = build_model(config, &cli_args, Path::new(".")).unwrap(); let mut cache = ZorkCache::default(); c.bench_function("Build project", |b| { diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index f6665f65..d2c301ac 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -148,7 +148,8 @@ impl<'a> ZorkCache<'a> { msvc::load_metadata(self, program_data)? } - if compiler != CppCompiler::MSVC && helpers::user_declared_system_headers_to_build(program_data) + if compiler != CppCompiler::MSVC + && helpers::user_declared_system_headers_to_build(program_data) { let i = Self::track_system_modules(program_data); self.compilers_metadata.system_modules.clear(); @@ -546,8 +547,8 @@ mod msvc { } mod helpers { - use std::borrow::Cow; use crate::project_model::ZorkModel; + use std::borrow::Cow; pub(crate) fn user_declared_system_headers_to_build(program_data: &ZorkModel<'_>) -> bool { program_data diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index d9b007d0..ed441407 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -872,13 +872,14 @@ mod helpers { } /// TODO - pub(crate) fn translation_unit_must_be_rebuilt( // TODO: separation of concerns? Please - // Just make two fns, the one that checks for the status and the one that checks for modifications - // then just use a template-factory design pattern by just abstracting away the two checks in one call - compiler: CppCompiler, - last_process_execution: &DateTime, - cached_source_cmd: &SourceCommandLine, - file: &Path, + pub(crate) fn translation_unit_must_be_rebuilt( + // TODO: separation of concerns? Please + // Just make two fns, the one that checks for the status and the one that checks for modifications + // then just use a template-factory design pattern by just abstracting away the two checks in one call + compiler: CppCompiler, + last_process_execution: &DateTime, + cached_source_cmd: &SourceCommandLine, + file: &Path, ) -> bool { if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { log::trace!("Module unit {:?} will be rebuilt since we've detected that you are using Clang in Windows", cached_source_cmd.path()); From dbadd8919461c58b37ecf02e0bedf6c0a3c8dde6 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 24 Jun 2024 21:49:59 +0200 Subject: [PATCH 13/73] fix: removed unused borrows of the project configuration since its ownership is transferred to the build_model procedure fix: changed back the types on the config file data structures to their original reference types --- .github/workflows/code-quality.yml | 2 +- zork++/src/lib/config_file/compiler.rs | 15 +++++----- zork++/src/lib/config_file/executable.rs | 17 +++++------ zork++/src/lib/config_file/mod.rs | 4 +-- zork++/src/lib/utils/reader.rs | 37 ++++++++++++++---------- 5 files changed, 40 insertions(+), 35 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index e1ff22ad..24b5bbfb 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -47,7 +47,7 @@ jobs: cargo fmt --all -- --check unit-and-doc-tests: - name: Verify code formatting + name: Run unit and doc tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/zork++/src/lib/config_file/compiler.rs b/zork++/src/lib/config_file/compiler.rs index eb52d276..6e790a47 100644 --- a/zork++/src/lib/config_file/compiler.rs +++ b/zork++/src/lib/config_file/compiler.rs @@ -1,6 +1,5 @@ //! file for represent the available configuration properties within Zork++ //! for setting up the target compiler -use std::borrow::Cow; use serde::{Deserialize, Serialize}; @@ -70,16 +69,16 @@ use crate::project_model; /// [`zork::config_file::ZorkConfigFile`] doc-test #[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[serde(deny_unknown_fields)] -pub struct CompilerAttribute { +pub struct CompilerAttribute<'a> { pub cpp_compiler: CppCompiler, - // #[serde(borrow)] - pub driver_path: Option>, + #[serde(borrow)] + pub driver_path: Option<&'a str>, pub cpp_standard: LanguageLevel, pub std_lib: Option, - // #[serde(borrow)] - pub extra_args: Option>>, - // #[serde(borrow)] - pub system_headers_path: Option>, + #[serde(borrow)] + pub extra_args: Option>, + #[serde(borrow)] + pub system_headers_path: Option<&'a str>, } /// The C++ compilers available within Zork++ diff --git a/zork++/src/lib/config_file/executable.rs b/zork++/src/lib/config_file/executable.rs index 5a81ac95..ce59b9c2 100644 --- a/zork++/src/lib/config_file/executable.rs +++ b/zork++/src/lib/config_file/executable.rs @@ -1,5 +1,4 @@ //! Specify the execution configuration -use std::borrow::Cow; use serde::*; @@ -44,12 +43,12 @@ use serde::*; #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] #[serde(deny_unknown_fields)] pub struct ExecutableAttribute<'a> { - // #[serde(borrow)] - pub executable_name: Option>, - // #[serde(borrow)] - pub sources_base_path: Option>, - // #[serde(borrow)] - pub sources: Option>>, - // #[serde(borrow)] - pub extra_args: Option>>, + #[serde(borrow)] + pub executable_name: Option<&'a str>, + #[serde(borrow)] + pub sources_base_path: Option<&'a str>, + #[serde(borrow)] + pub sources: Option>, + #[serde(borrow)] + pub extra_args: Option>, } diff --git a/zork++/src/lib/config_file/mod.rs b/zork++/src/lib/config_file/mod.rs index 2a4c4d33..dbc9c994 100644 --- a/zork++/src/lib/config_file/mod.rs +++ b/zork++/src/lib/config_file/mod.rs @@ -46,8 +46,8 @@ use self::{ pub struct ZorkConfigFile<'a> { #[serde(borrow)] pub project: ProjectAttribute<'a>, - // #[serde(borrow)] - pub compiler: CompilerAttribute, + #[serde(borrow)] + pub compiler: CompilerAttribute<'a>, #[serde(borrow)] pub build: Option>, #[serde(borrow)] diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index eee4e7b8..956cd65a 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -139,7 +139,10 @@ fn assemble_project_model(config: ProjectAttribute) -> ProjectModel { } } -fn assemble_compiler_model(config: CompilerAttribute, cli_args: &CliArgs) -> CompilerModel { +fn assemble_compiler_model<'a>( + config: CompilerAttribute<'a>, + cli_args: &'a CliArgs, +) -> CompilerModel<'a> { let extra_args = config .extra_args .map(|args| args.into_iter().map(Argument::from).collect()) @@ -179,18 +182,25 @@ fn assemble_executable_model<'a>( let config = config.as_ref(); let executable_name = config - .and_then(|exe| -> Option> { exe.executable_name.clone() }) + .and_then(|exe| exe.executable_name) + .map(Cow::Borrowed) .unwrap_or(project_name); let sources = config - .and_then(|exe| exe.sources.clone()) - .unwrap_or_else(|| Vec::with_capacity(0)); + .and_then(|exe| exe.sources.as_ref()) + .map(|srcs| { + srcs.iter()// TODO: abstract this kind of procedures away to some method of TranslationUnit, for example? + // or some other new trait (can't this have a default impl on the trait definition itself? + .map(|src| Cow::Borrowed(*src)) + .collect::>>() + }) + .unwrap_or_default(); let sourceset = get_sourceset_for(sources, project_root); let extra_args = config .and_then(|exe| exe.extra_args.as_ref()) - .map(|args| args.iter().map(Argument::from).collect()) + .map(|args| args.iter().map(|arg| Argument::from(*arg)).collect()) .unwrap_or_default(); ExecutableModel { @@ -352,7 +362,11 @@ fn assemble_tests_model<'a>( let sources = config .and_then(|exe| exe.sources.as_ref()) - .map(|srcs| srcs.iter().map(|src| Cow::Borrowed(*src)).collect()) + .map(|srcs| { + srcs.iter() + .map(|src| Cow::Borrowed(*src)) + .collect::>>() + }) .unwrap_or_default(); let sourceset = get_sourceset_for(sources, project_root); @@ -368,7 +382,7 @@ fn assemble_tests_model<'a>( } } -fn get_sourceset_for<'a>(srcs: Vec>, project_root: &Path) -> SourceSet<'a> { +fn get_sourceset_for<'a>(srcs: Vec>, project_root: &Path) -> SourceSet<'a> { let sources = srcs .iter() .map(|src| { @@ -452,14 +466,7 @@ mod test { sourceset: SourceSet { sources: vec![] }, extra_args: vec![], }, - modules: Some(ModulesModel { - base_ifcs_dir: Path::new("."), - interfaces: vec![], - base_impls_dir: Path::new("."), - implementations: vec![], - sys_modules: vec![], - extra_args: vec![], - }), + modules: None, tests: TestsModel { test_executable_name: "Zork++_test".into(), sourceset: SourceSet { sources: vec![] }, From 13d36b9ff23374129cf268bc739c455ce1ced2d7 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 26 Jun 2024 13:42:13 +0200 Subject: [PATCH 14/73] feat(wip): Renormalizing the types of the data that will be sent to the CLI --- zork++/src/lib/cache/mod.rs | 9 ++- zork++/src/lib/cli/output/arguments.rs | 25 ++++++ zork++/src/lib/cli/output/commands.rs | 100 +++++++++++++++++------- zork++/src/lib/compiler/data_factory.rs | 61 +++++++++------ zork++/src/lib/compiler/mod.rs | 23 +----- zork++/src/lib/utils/reader.rs | 2 +- 6 files changed, 147 insertions(+), 73 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index d2c301ac..1bbf6751 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -280,8 +280,10 @@ impl<'a> ZorkCache<'a> { let generated_commands = &self.generated_commands; generated_commands - .pre_tasks + .cpp_stdlib + .as_slice() .iter() + .chain(generated_commands.c_compat_stdlib.as_slice().iter()) .chain(generated_commands.interfaces.iter()) .chain(generated_commands.implementations.iter()) .chain(generated_commands.sources.iter()) @@ -293,8 +295,9 @@ impl<'a> ZorkCache<'a> { latest_commands.interfaces.len() + latest_commands.implementations.len() + latest_commands.sources.len() - + latest_commands.pre_tasks.len() - + 1 // TODO: the linker one? Does it supports it clangd? + // + latest_commands.pre_tasks.len() + + 2 // the cpp_stdlib and the c_compat_stdlib + // + 1 // TODO: the linker one? Does it supports it clangd? } /// Looks for the already precompiled `GCC` or `Clang` system headers, diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index 3abd69fb..2e4d7637 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -4,10 +4,13 @@ use std::borrow::Cow; use std::ops::Deref; use std::path::Path; +use std::rc::Rc; use std::{borrow::Borrow, ffi::OsStr, path::PathBuf}; use serde::{Deserialize, Serialize}; +use crate::project_model::compiler::LanguageLevel; + /// Wrapper type for represent and storing a command line argument #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Argument(String); @@ -60,12 +63,30 @@ impl From<&PathBuf> for Argument { } } +impl From for Argument { + fn from(value: LanguageLevel) -> Self { + Self::from(value.as_ref().to_string()) + } +} + impl Borrow for Argument { fn borrow(&self) -> &str { &self.0 } } +impl Borrow for &Argument { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl Borrow for Rc<&Argument> { + fn borrow(&self) -> &Argument { + &self + } +} + impl AsRef for Argument { fn as_ref(&self) -> &OsStr { OsStr::new(&self.0) @@ -123,6 +144,10 @@ impl Arguments { pub fn extend_from_slice(&mut self, slice: &[Argument]) { self.0.extend_from_slice(slice); } + + pub fn as_slice(&self) -> &[Argument] { + &self.0 + } } impl Deref for Arguments { diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index 758138b6..52f2d49b 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -40,28 +40,33 @@ pub fn run_generated_commands( execute_command(compiler, program_data, sys_module.1, cache)?; } + let general_args = commands.general_args.get_args(); + let translation_units = commands - .pre_tasks - .iter_mut() - .chain(commands.interfaces.iter_mut()) - .chain(commands.implementations.iter_mut()) - .chain(commands.sources.iter_mut()); + .get_all_command_lines() + .filter(|scl| scl.need_to_build) + .collect::>(); for translation_unit_cmd in translation_units { - if translation_unit_cmd.need_to_build { - let r = execute_command(compiler, program_data, &translation_unit_cmd.args, cache); - translation_unit_cmd.execution_result = CommandExecutionResult::from(&r); - if let Err(e) = r { - cache::save(program_data, cache, commands, test_mode)?; - return Err(e); - } else if !r.as_ref().unwrap().success() { - let err = eyre!( - "Ending the program, because the build of: {:?} wasn't ended successfully", - translation_unit_cmd.filename - ); - cache::save(program_data, cache, commands, test_mode)?; - return Err(err); - } + let translation_unit_cmd_args = + general_args + .iter() + .chain(translation_unit_cmd.args.iter()) + .collect::>(); + + let r = execute_command(compiler, program_data, &translation_unit_cmd_args, cache); + translation_unit_cmd.execution_result = CommandExecutionResult::from(&r); + + if let Err(e) = r { + cache::save(program_data, cache, commands, test_mode)?; + return Err(e); + } else if !r.as_ref().unwrap().success() { + let err = eyre!( + "Ending the program, because the build of: {:?} failed", + translation_unit_cmd.filename + ); + cache::save(program_data, cache, commands, test_mode)?; + return Err(err); } } @@ -77,7 +82,7 @@ pub fn run_generated_commands( } else if !r.as_ref().unwrap().success() { cache::save(program_data, cache, commands, test_mode)?; return Err(eyre!( - "Ending the program, because the linker command line execution wasn't ended successfully", + "Ending the program, because the linker command line execution failed", )); } } @@ -117,18 +122,21 @@ pub fn autorun_generated_binary( /// Executes a new [`std::process::Command`] configured according the chosen /// compiler and the current operating system -fn execute_command( +fn execute_command( compiler: CppCompiler, model: &ZorkModel, - arguments: &[Argument], + arguments: &[T], cache: &ZorkCache, -) -> Result { +) -> Result +where + T: AsRef + std::fmt::Debug, // + Join , >::Output: std::fmt::Display // unstable feature yet +{ log::trace!( "[{compiler}] - Executing command => {:?}", format!( - "{} {}", + "{} {:?}", compiler.get_driver(&model.compiler), - arguments.join(" ") + arguments // T::join(&arguments, " ") ) ); @@ -139,7 +147,7 @@ fn execute_command( .envs(cache.get_process_env_args()) .spawn()? .wait() - .with_context(|| format!("[{compiler}] - Command {:?} failed!", arguments.join(" "))) + .with_context(|| format!("[{compiler}] - Command {arguments:?} failed!")) } /// The pieces and details for the generated command line @@ -157,6 +165,27 @@ pub struct SourceCommandLine { } impl SourceCommandLine { + /// Chains the read-only iterators of the flyweights that holds the shared arguments, like [`CommonArgs`] and + /// the specific compiler ones [`CompilerCommonArguments`] to the specific ones of self.args, + /// in order to create a temporary view with all the command lines arguments that are required + /// to build a *C++ translation unit* + /* pub fn all_command_args<'a>( + &'a self, + general_args: &'a [Argument], + _compiler_specific_common_args: &'a [Argument], + ) -> impl Iterator + Debug + 'a { + general_args.iter().chain(self.args.iter()) + } */ + /* pub fn all_command_args<'a>( + &'a self, + commands: &'a Commands + ) + -> impl Iterator + // -> Vec<&'a Argument> + { + commands.general_args.get_args().iter().chain(self.args.iter()) + } */ + pub fn from_translation_unit( // TODO init it as a args holder, but doesn't have the status yet tu: impl TranslationUnit, @@ -223,7 +252,8 @@ impl LinkerCommandLine { #[derive(Serialize, Deserialize, Default)] pub struct Commands { pub compiler: CppCompiler, - pub pre_tasks: Vec, // TODO: since there's no really pre-tasks (only build the std_lib), create named entries for std and std.compat + pub cpp_stdlib: Option, + pub c_compat_stdlib: Option, pub system_modules: HashMap, pub general_args: CommonArgs, @@ -247,10 +277,12 @@ impl Commands { // of every collection compiler: model.compiler.cpp_compiler, + cpp_stdlib: None, + c_compat_stdlib: None, + general_args, compiler_common_args: compiler_specific_common_args, - pre_tasks: Vec::with_capacity(0), system_modules: HashMap::with_capacity(0), interfaces: Vec::with_capacity(0), implementations: Vec::with_capacity(0), @@ -259,6 +291,18 @@ impl Commands { } } + pub fn get_all_command_lines( + &mut self, + ) -> impl Iterator + Debug + '_ { + self.cpp_stdlib + .as_mut_slice() + .iter_mut() + .chain(self.c_compat_stdlib.as_mut_slice().iter_mut()) + .chain(self.interfaces.as_mut_slice().iter_mut()) + .chain(self.implementations.as_mut_slice().iter_mut()) + .chain(self.sources.as_mut_slice().iter_mut()) + } + pub fn add_linker_file_path(&mut self, path: &Path) { self.linker.add_buildable_at(path); } diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index f6e9120a..226658a4 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -2,43 +2,60 @@ //! translation unit, having shared data without replicating it until the final command line must //! be generated in order to be stored (in cache) and executed (in the underlying shell) -use std::{ - borrow::Cow, - path::{Path, PathBuf}, -}; +use std::{borrow::Cow, path::Path, rc::Rc}; use serde::{Deserialize, Serialize}; use crate::{ + bounds::ExtraArgs, cli::output::arguments::{clang_args, Argument, Arguments}, - project_model::compiler::{CppCompiler, LanguageLevel, StdLib}, + project_model::compiler::{CppCompiler, StdLib}, project_model::ZorkModel, }; /// Holds the common arguments across all the different command lines regarding the target compiler #[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct CommonArgs { - compiler: CppCompiler, - out_dir: PathBuf, - // out_dir: &'a PathBuf, - language_level: LanguageLevel, - extra_args: Arguments, - // extra_args: &'a [Argument], +pub struct CommonArgs(Arguments); +impl CommonArgs { + pub fn get_args(&self) -> Arguments { + self.0.clone() + } + + // pub fn get_args_slice(&self) -> &[Rc] { + pub fn get_args_slice(&self) -> impl Iterator> { + self.0.as_slice().iter().map(|arg| Rc::new(arg)) + } } impl<'a> From<&'a ZorkModel<'_>> for CommonArgs { fn from(model: &'a ZorkModel<'_>) -> Self { - let compiler = model.compiler.cpp_compiler; - let out_dir = model.build.output_dir.clone(); - let language_level = model.compiler.cpp_standard; - let extra_args = model.compiler.extra_args.clone(); + let mut common_args = Arguments::default(); + common_args.push(model.compiler.language_level_arg()); + common_args.extend_from_slice(model.compiler.extra_args()); - Self { - compiler, - out_dir, - language_level, - extra_args: Arguments::from_vec(extra_args), - } + Self (common_args) + } +} + +impl IntoIterator for CommonArgs { + type Item = Argument; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +/// Factory function for bring the data structure that holds the common arguments of a source +/// command line for every translation unit, regardeless the underlying choosen compiler +pub fn compiler_common_arguments_factory( + model: &ZorkModel<'_>, +) -> Box { + // TODO: consider having a union (enum) instead of a fat ptr + match model.compiler.cpp_compiler { + CppCompiler::CLANG => Box::new(ClangCommonArgs::new(model)), + CppCompiler::MSVC => Box::new(MsvcCommonArgs::new()), + CppCompiler::GCC => Box::new(GccCommonArgs::new()), } } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index ed441407..a0d81f18 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -24,9 +24,7 @@ use crate::{ }, }; -use self::data_factory::{ - ClangCommonArgs, CommonArgs, CompilerCommonArguments, GccCommonArgs, MsvcCommonArgs, -}; +use self::data_factory::{CommonArgs, CompilerCommonArguments}; /// The entry point of the compilation process /// @@ -40,7 +38,7 @@ pub fn build_project<'a>( // Generate the Flyweight struct, with the repetitive data and details of the command lines let general_args = CommonArgs::from(model); let compiler_specific_common_args: Box = - compiler_common_arguments_factory(model); + data_factory::compiler_common_arguments_factory(model); // A registry of the generated command lines let mut commands = Commands::new(model, general_args, compiler_specific_common_args); @@ -67,17 +65,6 @@ pub fn build_project<'a>( Ok(commands) } -/// Factory function for bring the data structure that holds the common arguments of a source -/// command line for every translation unit, regardeless the underlying choosen compiler -fn compiler_common_arguments_factory(model: &ZorkModel<'_>) -> Box { - // TODO: consider having a union (enum) instead of a fat ptr - match model.compiler.cpp_compiler { - CppCompiler::CLANG => Box::new(ClangCommonArgs::new(model)), - CppCompiler::MSVC => Box::new(MsvcCommonArgs::new()), - CppCompiler::GCC => Box::new(GccCommonArgs::new()), - } -} - /// Builds the C++ standard library as a pre-step acording to the specification /// of each compiler vendor fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: &mut Commands) { @@ -107,8 +94,7 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: }; source_command_line }; - log::info!("Generated std SourceCommandLine: {cpp_stdlib:?}"); - commands.pre_tasks.push(cpp_stdlib); + commands.cpp_stdlib = Some(cpp_stdlib); let built_stdlib_compat_path = &cache.compilers_metadata.msvc.c_stdlib_bmi_path; let c_compat = if !built_stdlib_path.exists() { @@ -128,8 +114,7 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: }; source_command_line }; - log::info!("Generated std SourceCommandLine: {c_compat:?}"); - commands.pre_tasks.push(c_compat); + commands.c_compat_stdlib = Some(c_compat); } } diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 956cd65a..95c4ecb9 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -189,7 +189,7 @@ fn assemble_executable_model<'a>( let sources = config .and_then(|exe| exe.sources.as_ref()) .map(|srcs| { - srcs.iter()// TODO: abstract this kind of procedures away to some method of TranslationUnit, for example? + srcs.iter() // TODO: abstract this kind of procedures away to some method of TranslationUnit, for example? // or some other new trait (can't this have a default impl on the trait definition itself? .map(|src| Cow::Borrowed(*src)) .collect::>>() From 985c334b91f9e606f3733c8d15477c6b588e6c9b Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 26 Jun 2024 16:20:32 +0200 Subject: [PATCH 15/73] feat(wip): Flexible inputs on the commands executor --- zork++/src/lib/cache/mod.rs | 27 +++++++- zork++/src/lib/cli/output/arguments.rs | 69 ++++++++++++++++---- zork++/src/lib/cli/output/commands.rs | 84 ++++++++++++++++++------- zork++/src/lib/compiler/data_factory.rs | 5 +- zork++/src/lib/lib.rs | 21 ++++--- 5 files changed, 158 insertions(+), 48 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 1bbf6751..99592eeb 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -5,8 +5,10 @@ pub mod compile_commands; use chrono::{DateTime, Utc}; use color_eyre::{eyre::Context, Result}; +use std::cell::RefCell; use std::collections::HashMap; use std::fmt::Debug; +use std::rc::Rc; use std::{ fs, fs::File, @@ -65,6 +67,29 @@ pub fn load<'a>(program_data: &'a ZorkModel<'_>, cli_args: &CliArgs) -> Result, + cache: Rc>, + commands: Commands, + _test_mode: bool, +) -> Result<()> { + let cache_path = &program_data + .build + .output_dir + .join("zork") + .join("cache") + .join(program_data.compiler.cpp_compiler.as_ref()) + .join(constants::ZORK_CACHE_FILENAME); + + /* cache.run_final_tasks(program_data, commands, test_mode)?; + cache.last_program_execution = Utc::now(); */ + cache.borrow_mut().generated_commands = commands; + let c: &ZorkCache = &cache.borrow_mut(); + utils::fs::serialize_object_to_file(cache_path, c) + .with_context(move || "Error saving data to the Zork++ cache") + // Ok(()) +} + /// Standalone utility for persist the cache to the file system pub fn save( program_data: &ZorkModel<'_>, @@ -87,7 +112,7 @@ pub fn save( .with_context(move || "Error saving data to the Zork++ cache") } -#[derive(Serialize, Deserialize, Default)] +#[derive(Serialize, Deserialize, Clone, Default)] pub struct ZorkCache<'a> { pub compiler: CppCompiler, pub last_program_execution: DateTime, diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index 2e4d7637..da529533 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -4,17 +4,22 @@ use std::borrow::Cow; use std::ops::Deref; use std::path::Path; -use std::rc::Rc; use std::{borrow::Borrow, ffi::OsStr, path::PathBuf}; use serde::{Deserialize, Serialize}; use crate::project_model::compiler::LanguageLevel; +pub trait CommandLineArgument: std::fmt::Display {} +pub trait CommandLineArguments: std::fmt::Display {} + /// Wrapper type for represent and storing a command line argument #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Argument(String); +impl CommandLineArgument for Argument {} +impl CommandLineArgument for &Argument {} + impl Argument { pub fn value(&self) -> &String { &self.0 @@ -75,24 +80,18 @@ impl Borrow for Argument { } } -impl Borrow for &Argument { - fn borrow(&self) -> &str { - &self.0 - } -} - -impl Borrow for Rc<&Argument> { - fn borrow(&self) -> &Argument { - &self - } -} - impl AsRef for Argument { fn as_ref(&self) -> &OsStr { OsStr::new(&self.0) } } +impl AsRef for Argument { + fn as_ref(&self) -> &str { + &self.0 + } +} + impl core::fmt::Display for Argument { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) @@ -102,6 +101,16 @@ impl core::fmt::Display for Argument { /// Strong type for represent a linear collection of [`Argument`] #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Arguments(Vec); + +impl CommandLineArguments for Arguments {} +impl CommandLineArguments for &Arguments {} + +impl core::fmt::Display for Arguments { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.iter().try_for_each(|arg| write!(f, "{}", arg)) + } +} + impl Arguments { /// Wraps an existing [`std::vec::Vec`] of [`Argument`] pub fn from_vec(vec: Vec) -> Self { @@ -167,6 +176,40 @@ impl IntoIterator for Arguments { } } +impl IntoIterator for &Arguments { + type Item = Argument; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.clone().into_iter() + } +} +/* impl FromIterator> for Arguments { + fn from_iter>(iter: T) -> Self { + todo!() + } +} */ + +impl FromIterator for Arguments { + fn from_iter>(iter: I) -> Self { + let mut vec = Vec::new(); + for item in iter { + vec.push(item); + } + Arguments(vec) + } +} + +impl<'a> FromIterator<&'a Argument> for Arguments { + fn from_iter>(iter: I) -> Arguments { + let mut vec = Vec::new(); + for item in iter { + vec.push(item.clone()); + } + Arguments(vec) + } +} + /// Isolated module to storing custom procedures to easily create and add new command line arguments /// or flags specific to Clang, that otherwise, will be bloating the main procedures with a lot /// of cognitive complexity diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index 52f2d49b..202ddda4 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -1,9 +1,12 @@ //! Contains helpers and data structures to be processed in a nice and neat way the commands generated to be executed //! by Zork++ +use std::borrow::BorrowMut; +use std::cell::RefCell; use std::collections::HashMap; use std::ffi::OsStr; use std::fmt::Debug; +use std::rc::Rc; use std::slice::Iter; use std::{ path::{Path, PathBuf}, @@ -24,12 +27,12 @@ use color_eyre::{ }; use serde::{Deserialize, Serialize}; -use super::arguments::Argument; +use super::arguments::{Argument, CommandLineArgument, CommandLineArguments}; pub fn run_generated_commands( program_data: &ZorkModel<'_>, mut commands: Commands, // TODO: &mut, and then directly store them? - cache: &mut ZorkCache, + cache: Rc>, test_mode: bool, ) -> Result { log::info!("Proceeding to execute the generated commands..."); @@ -37,7 +40,7 @@ pub fn run_generated_commands( for sys_module in &commands.system_modules { // TODO: will be deprecated soon, hopefully - execute_command(compiler, program_data, sys_module.1, cache)?; + execute_command(compiler, program_data, sys_module.1, &cache.borrow())?; } let general_args = commands.general_args.get_args(); @@ -47,25 +50,31 @@ pub fn run_generated_commands( .filter(|scl| scl.need_to_build) .collect::>(); + log::warn!("Detected lines: {:?}", translation_units); + for translation_unit_cmd in translation_units { - let translation_unit_cmd_args = - general_args + let translation_unit_cmd_args: Arguments = general_args .iter() .chain(translation_unit_cmd.args.iter()) - .collect::>(); - - let r = execute_command(compiler, program_data, &translation_unit_cmd_args, cache); + .collect(); + + let r = execute_command( + compiler, + program_data, + &translation_unit_cmd_args, + &cache.borrow(), + ); translation_unit_cmd.execution_result = CommandExecutionResult::from(&r); if let Err(e) = r { - cache::save(program_data, cache, commands, test_mode)?; + cache::save2(program_data, cache, commands, test_mode)?; return Err(e); } else if !r.as_ref().unwrap().success() { let err = eyre!( "Ending the program, because the build of: {:?} failed", translation_unit_cmd.filename ); - cache::save(program_data, cache, commands, test_mode)?; + cache::save2(program_data, cache, commands, test_mode)?; return Err(err); } } @@ -73,21 +82,26 @@ pub fn run_generated_commands( if !commands.linker.args.is_empty() { log::debug!("Processing the linker command line..."); - let r = execute_command(compiler, program_data, &commands.linker.args, cache); + let r = execute_command( + compiler, + program_data, + &commands.linker.args, + &cache.borrow(), + ); commands.linker.execution_result = CommandExecutionResult::from(&r); if let Err(e) = r { - cache::save(program_data, cache, commands, test_mode)?; + cache::save2(program_data, cache, commands, test_mode)?; return Err(e); } else if !r.as_ref().unwrap().success() { - cache::save(program_data, cache, commands, test_mode)?; + cache::save2(program_data, cache, commands, test_mode)?; return Err(eyre!( "Ending the program, because the linker command line execution failed", )); } } - cache::save(program_data, cache, commands, test_mode)?; + cache::save2(program_data, cache, commands, test_mode)?; Ok(CommandExecutionResult::Success) } @@ -122,21 +136,43 @@ pub fn autorun_generated_binary( /// Executes a new [`std::process::Command`] configured according the chosen /// compiler and the current operating system -fn execute_command( +fn execute_command( compiler: CppCompiler, model: &ZorkModel, - arguments: &[T], + arguments: T, cache: &ZorkCache, ) -> Result where - T: AsRef + std::fmt::Debug, // + Join , >::Output: std::fmt::Display // unstable feature yet + T: IntoIterator + std::fmt::Display + std::marker::Copy, + S: AsRef, { + log::trace!( + "[{compiler}] - Executing command => {:?}", + format!("{} {arguments}", compiler.get_driver(&model.compiler),) + ); + + let driver = compiler.get_driver(&model.compiler); + let os_driver = OsStr::new(driver.as_ref()); + std::process::Command::new(os_driver) + .args(arguments) + .envs(cache.get_process_env_args()) + .spawn()? + .wait() + .with_context(|| format!("[{compiler}] - Command {arguments} failed!")) +} + +fn execute_command2( + compiler: CppCompiler, + model: &ZorkModel, + arguments: &[Argument], + cache: &ZorkCache, +) -> Result { log::trace!( "[{compiler}] - Executing command => {:?}", format!( - "{} {:?}", + "{} {}", compiler.get_driver(&model.compiler), - arguments // T::join(&arguments, " ") + arguments.join(" ") ) ); @@ -147,7 +183,7 @@ where .envs(cache.get_process_env_args()) .spawn()? .wait() - .with_context(|| format!("[{compiler}] - Command {arguments:?} failed!")) + .with_context(|| format!("[{compiler}] - Command {} failed!", arguments.join(" "))) } /// The pieces and details for the generated command line @@ -230,7 +266,7 @@ impl SourceCommandLine { pub struct LinkerCommandLine { // pub main: &'a Path, // TODO: can't this disappear? At the end of the day, is just another obj file pub built_files: Vec, - pub args: Vec, + pub args: Arguments, pub execution_result: CommandExecutionResult, } @@ -249,7 +285,7 @@ impl LinkerCommandLine { } /// Holds the generated command line arguments for a concrete compiler -#[derive(Serialize, Deserialize, Default)] +#[derive(Serialize, Clone, Deserialize, Default)] pub struct Commands { pub compiler: CppCompiler, pub cpp_stdlib: Option, @@ -257,7 +293,7 @@ pub struct Commands { pub system_modules: HashMap, pub general_args: CommonArgs, - pub compiler_common_args: Box, + // pub compiler_common_args: Box, pub interfaces: Vec, pub implementations: Vec, @@ -281,7 +317,7 @@ impl Commands { c_compat_stdlib: None, general_args, - compiler_common_args: compiler_specific_common_args, + // compiler_common_args: compiler_specific_common_args, system_modules: HashMap::with_capacity(0), interfaces: Vec::with_capacity(0), diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index 226658a4..582217cf 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -21,9 +21,8 @@ impl CommonArgs { self.0.clone() } - // pub fn get_args_slice(&self) -> &[Rc] { pub fn get_args_slice(&self) -> impl Iterator> { - self.0.as_slice().iter().map(|arg| Rc::new(arg)) + self.0.as_slice().iter().map(Rc::new) } } @@ -33,7 +32,7 @@ impl<'a> From<&'a ZorkModel<'_>> for CommonArgs { common_args.push(model.compiler.language_level_arg()); common_args.extend_from_slice(model.compiler.extra_args()); - Self (common_args) + Self(common_args) } } diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index fad3b581..2b49a2b6 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -16,13 +16,13 @@ pub mod utils; /// data sent to stdout/stderr pub mod worker { use crate::{config_file, utils::fs::get_project_root_absolute_path}; - use std::{fs, path::Path}; + use std::{cell::RefCell, fs, path::Path, rc::Rc}; use crate::{ cache::{self, ZorkCache}, cli::{ input::{CliArgs, Command}, - output::commands::{self, autorun_generated_binary, CommandExecutionResult, Commands}, + output::commands::{self, CommandExecutionResult, Commands}, }, compiler::build_project, project_model::{compiler::CppCompiler, ZorkModel}, @@ -112,19 +112,26 @@ pub mod worker { // TODO: the return type isn't as clever as it could be let commands: Commands; + let rc_cache = Rc::new(RefCell::new(cache.clone())); // TODO: provisional clone + match cli_args.command { Command::Build => { commands = build_project(program_data, &mut cache, false) .with_context(|| "Failed to build project")?; - commands::run_generated_commands(program_data, commands, &mut cache, false) + commands::run_generated_commands( + program_data, + commands, + rc_cache.clone(), + false, + ) } Command::Run => { commands = build_project(program_data, &mut cache, false) .with_context(|| "Failed to build project")?; - match commands::run_generated_commands(program_data, commands, &mut cache, false) { - Ok(_) => autorun_generated_binary( + match commands::run_generated_commands(program_data, commands, rc_cache.clone(), false) { + Ok(_) => commands::autorun_generated_binary( &program_data.compiler.cpp_compiler, &program_data.build.output_dir, &program_data.executable.executable_name, @@ -136,8 +143,8 @@ pub mod worker { commands = build_project(program_data, &mut cache, true) .with_context(|| "Failed to build project")?; - match commands::run_generated_commands(program_data, commands, &mut cache, true) { - Ok(_) => autorun_generated_binary( + match commands::run_generated_commands(program_data, commands, rc_cache.clone(), true) { + Ok(_) => commands::autorun_generated_binary( &program_data.compiler.cpp_compiler, &program_data.build.output_dir, &program_data.tests.test_executable_name, From 4815b9f0ce7e450f718d13868ef52c242e46cce9 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 26 Jun 2024 16:41:33 +0200 Subject: [PATCH 16/73] feat(wip): Flexible inputs on the commands executor --- zork++/src/lib/cli/output/commands.rs | 26 ++++++++------ zork++/src/lib/compiler/mod.rs | 52 +++++++-------------------- zork++/src/lib/lib.rs | 12 ++++--- 3 files changed, 35 insertions(+), 55 deletions(-) diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index 202ddda4..c547dbbc 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -6,6 +6,7 @@ use std::cell::RefCell; use std::collections::HashMap; use std::ffi::OsStr; use std::fmt::Debug; +use std::ops::DerefMut; use std::rc::Rc; use std::slice::Iter; use std::{ @@ -31,16 +32,19 @@ use super::arguments::{Argument, CommandLineArgument, CommandLineArguments}; pub fn run_generated_commands( program_data: &ZorkModel<'_>, - mut commands: Commands, // TODO: &mut, and then directly store them? - cache: Rc>, + // mut commands: Commands, // TODO: &mut, and then directly store them? + mut cache: ZorkCache, test_mode: bool, ) -> Result { log::info!("Proceeding to execute the generated commands..."); - let compiler = commands.compiler; + let compiler = cache.compiler; + let mut commands = cache.generated_commands; + + let rc_cache = Rc::new(RefCell::new(cache)); for sys_module in &commands.system_modules { // TODO: will be deprecated soon, hopefully - execute_command(compiler, program_data, sys_module.1, &cache.borrow())?; + execute_command(compiler, program_data, sys_module.1, &rc_cache.borrow())?; } let general_args = commands.general_args.get_args(); @@ -62,19 +66,19 @@ pub fn run_generated_commands( compiler, program_data, &translation_unit_cmd_args, - &cache.borrow(), + &rc_cache.borrow(), ); translation_unit_cmd.execution_result = CommandExecutionResult::from(&r); if let Err(e) = r { - cache::save2(program_data, cache, commands, test_mode)?; + cache::save(program_data, &mut cache, commands, test_mode)?; return Err(e); } else if !r.as_ref().unwrap().success() { let err = eyre!( "Ending the program, because the build of: {:?} failed", translation_unit_cmd.filename ); - cache::save2(program_data, cache, commands, test_mode)?; + cache::save(program_data, &mut cache, commands, test_mode)?; return Err(err); } } @@ -86,22 +90,22 @@ pub fn run_generated_commands( compiler, program_data, &commands.linker.args, - &cache.borrow(), + &rc_cache.borrow(), ); commands.linker.execution_result = CommandExecutionResult::from(&r); if let Err(e) = r { - cache::save2(program_data, cache, commands, test_mode)?; + cache::save(program_data, &mut cache, commands, test_mode)?; return Err(e); } else if !r.as_ref().unwrap().success() { - cache::save2(program_data, cache, commands, test_mode)?; + cache::save(program_data, &mut cache, commands, test_mode)?; return Err(eyre!( "Ending the program, because the linker command line execution failed", )); } } - cache::save2(program_data, cache, commands, test_mode)?; + cache::save(program_data, rc_cache.borrow_mut(), commands, test_mode)?; Ok(CommandExecutionResult::Success) } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index a0d81f18..625270da 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -46,7 +46,7 @@ pub fn build_project<'a>( // TODO: add them to the commands DS, so they are together until they're generated // Build the std library as a module - build_modular_stdlib(model, cache, &mut commands); // TODO: ward it with an if for only call this fn for the + build_modular_stdlib(model, cache); // TODO: ward it with an if for only call this fn for the // 1st - Build the modules if let Some(modules) = &model.modules { @@ -67,54 +67,26 @@ pub fn build_project<'a>( /// Builds the C++ standard library as a pre-step acording to the specification /// of each compiler vendor -fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache, commands: &mut Commands) { +fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache) { let compiler = model.compiler.cpp_compiler; // TODO: remaining ones: Clang, GCC // TODO: try to abstract the procedures into just one entity if compiler.eq(&CppCompiler::MSVC) { let built_stdlib_path = &cache.compilers_metadata.msvc.stdlib_bmi_path; - let cpp_stdlib = if !built_stdlib_path.exists() { - log::trace!( - "Building the {:?} C++ standard library implementation", - compiler - ); - msvc_args::generate_std_cmd(model, cache, StdLibMode::Cpp) // TODO move mod msvc_args to commands - } else { - let source_command_line = SourceCommandLine { - directory: built_stdlib_path.file_stem().unwrap().into(), - filename: built_stdlib_path - .file_name() - .unwrap() - .to_string_lossy() - .to_string(), - args: Arguments::default(), - need_to_build: false, - execution_result: CommandExecutionResult::Cached, - }; - source_command_line - }; - commands.cpp_stdlib = Some(cpp_stdlib); + + if !built_stdlib_path.exists() { + log::trace!("Building the {:?} C++ standard library implementation", compiler); + let cpp_stdlib = msvc_args::generate_std_cmd(model, cache, StdLibMode::Cpp); + cache.generated_commands.cpp_stdlib = Some(cpp_stdlib); + } let built_stdlib_compat_path = &cache.compilers_metadata.msvc.c_stdlib_bmi_path; - let c_compat = if !built_stdlib_path.exists() { + if !built_stdlib_compat_path.exists() { log::trace!("Building the {:?} C compat CPP std lib", compiler); - msvc_args::generate_std_cmd(model, cache, StdLibMode::CCompat) - } else { - let source_command_line = SourceCommandLine { - directory: built_stdlib_compat_path.file_stem().unwrap().into(), - filename: built_stdlib_compat_path - .file_name() - .unwrap() - .to_string_lossy() - .to_string(), - args: Arguments::default(), - need_to_build: false, - execution_result: CommandExecutionResult::Cached, - }; - source_command_line - }; - commands.c_compat_stdlib = Some(c_compat); + let c_compat = msvc_args::generate_std_cmd(model, cache, StdLibMode::CCompat); + cache.generated_commands.c_compat_stdlib = Some(c_compat); + } } } diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 2b49a2b6..3b26fcb6 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -121,8 +121,8 @@ pub mod worker { commands::run_generated_commands( program_data, - commands, - rc_cache.clone(), + // commands, + cache, false, ) } @@ -130,7 +130,9 @@ pub mod worker { commands = build_project(program_data, &mut cache, false) .with_context(|| "Failed to build project")?; - match commands::run_generated_commands(program_data, commands, rc_cache.clone(), false) { + match commands::run_generated_commands(program_data, + // commands, + cache, false) { Ok(_) => commands::autorun_generated_binary( &program_data.compiler.cpp_compiler, &program_data.build.output_dir, @@ -143,7 +145,9 @@ pub mod worker { commands = build_project(program_data, &mut cache, true) .with_context(|| "Failed to build project")?; - match commands::run_generated_commands(program_data, commands, rc_cache.clone(), true) { + match commands::run_generated_commands(program_data, + // commands, + cache, true) { Ok(_) => commands::autorun_generated_binary( &program_data.compiler.cpp_compiler, &program_data.build.output_dir, From fb3c748f7ae878b71b0143581d9e8b845a992069 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sat, 29 Jun 2024 10:39:24 +0200 Subject: [PATCH 17/73] feat(wip): Working with the interior mutability pattern to see if it's worth the change of not having commands --- zork++/src/lib/cache/mod.rs | 46 ++++---- zork++/src/lib/cli/output/commands.rs | 161 +++++++++++++------------- zork++/src/lib/compiler/mod.rs | 143 ++++++++++++----------- zork++/src/lib/lib.rs | 31 ++--- zork++/src/lib/utils/fs.rs | 2 +- 5 files changed, 193 insertions(+), 190 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 99592eeb..91f593b4 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -5,10 +5,9 @@ pub mod compile_commands; use chrono::{DateTime, Utc}; use color_eyre::{eyre::Context, Result}; -use std::cell::RefCell; use std::collections::HashMap; use std::fmt::Debug; -use std::rc::Rc; + use std::{ fs, fs::File, @@ -69,9 +68,9 @@ pub fn load<'a>(program_data: &'a ZorkModel<'_>, cli_args: &CliArgs) -> Result, - cache: Rc>, - commands: Commands, - _test_mode: bool, + mut cache: ZorkCache, + // _commands: Commands, + test_mode: bool, ) -> Result<()> { let cache_path = &program_data .build @@ -81,20 +80,26 @@ pub fn save2( .join(program_data.compiler.cpp_compiler.as_ref()) .join(constants::ZORK_CACHE_FILENAME); - /* cache.run_final_tasks(program_data, commands, test_mode)?; - cache.last_program_execution = Utc::now(); */ - cache.borrow_mut().generated_commands = commands; - let c: &ZorkCache = &cache.borrow_mut(); - utils::fs::serialize_object_to_file(cache_path, c) - .with_context(move || "Error saving data to the Zork++ cache") + // let ro_cache = Rc::clone(&cache); + // let mut cache = cache.borrow_mut(); + cache.run_final_tasks(program_data, test_mode)?; + cache.last_program_execution = Utc::now(); + + // let cache_kind = &(*ro_cache).borrow(); // saves the temporary + serialize_cache(cache_path, &cache.clone()) // Ok(()) } +fn serialize_cache(path: &Path, cache: &ZorkCache) -> Result<()> { + utils::fs::serialize_object_to_file(path, cache) + .with_context(move || "Error saving data to the Zork++ cache") +} + /// Standalone utility for persist the cache to the file system pub fn save( program_data: &ZorkModel<'_>, - cache: &mut ZorkCache, - commands: Commands, + mut cache: ZorkCache, + // commands: Commands, test_mode: bool, ) -> Result<()> { let cache_path = &program_data @@ -105,10 +110,10 @@ pub fn save( .join(program_data.compiler.cpp_compiler.as_ref()) .join(constants::ZORK_CACHE_FILENAME); - cache.run_final_tasks(program_data, commands, test_mode)?; + cache.run_final_tasks(program_data, test_mode)?; cache.last_program_execution = Utc::now(); - utils::fs::serialize_object_to_file(cache_path, cache) + utils::fs::serialize_object_to_file(cache_path, &cache) .with_context(move || "Error saving data to the Zork++ cache") } @@ -188,7 +193,7 @@ impl<'a> ZorkCache<'a> { fn run_final_tasks( &mut self, program_data: &ZorkModel<'_>, - commands: Commands, + // commands: Commands, test_mode: bool, ) -> Result<()> { // if self.save_generated_commands(commands, program_data, test_mode) @@ -197,8 +202,7 @@ impl<'a> ZorkCache<'a> { // compile_commands::map_generated_commands_to_compilation_db(self)?; // } // - if let Some(_new_commands) = self.save_generated_commands(commands, program_data, test_mode) - { + if let Some(_new_commands) = self.save_generated_commands(program_data, test_mode) { if program_data.project.compilation_db { // TODO:: pass the new commands compile_commands::map_generated_commands_to_compilation_db(self)?; @@ -227,12 +231,12 @@ impl<'a> ZorkCache<'a> { /// the compile commands must be regenerated fn save_generated_commands( &mut self, - commands: Commands, + // commands: Commands, _model: &ZorkModel, _test_mode: bool, // TODO: tests linker cmd? ) -> Option { log::debug!("Storing in the cache the last generated command lines..."); - self.compiler = commands.compiler; + // self.compiler = commands.compiler; // let _process_no = if !self.generated_commands.is_empty() { // // TODO: do we now need this one? // // self.generated_commands.last().unwrap().cached_process_num + 1 @@ -252,7 +256,7 @@ impl<'a> ZorkCache<'a> { // TODO missing the one that determines if there's a new compilation database that must be generated // something like and iter that counts if at least one has been modified ?? // let at_least_one_changed = commands. - self.generated_commands = commands; + // self.generated_commands = commands; self.get_all_commands_iter() // TODO: Review the conditions and ensure that are the ones that we're looking for .any(|cmd| { diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index c547dbbc..feecaad1 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -1,12 +1,10 @@ //! Contains helpers and data structures to be processed in a nice and neat way the commands generated to be executed //! by Zork++ -use std::borrow::BorrowMut; use std::cell::RefCell; use std::collections::HashMap; use std::ffi::OsStr; use std::fmt::Debug; -use std::ops::DerefMut; use std::rc::Rc; use std::slice::Iter; use std::{ @@ -14,7 +12,9 @@ use std::{ process::ExitStatus, }; +use super::arguments::Argument; use crate::bounds::TranslationUnit; +use crate::cache::EnvVars; use crate::cli::output::arguments::Arguments; use crate::compiler::data_factory::{CommonArgs, CompilerCommonArguments}; use crate::{ @@ -28,87 +28,110 @@ use color_eyre::{ }; use serde::{Deserialize, Serialize}; -use super::arguments::{Argument, CommandLineArgument, CommandLineArguments}; - pub fn run_generated_commands( program_data: &ZorkModel<'_>, - // mut commands: Commands, // TODO: &mut, and then directly store them? - mut cache: ZorkCache, + mut cache: ZorkCache<'_>, test_mode: bool, ) -> Result { log::info!("Proceeding to execute the generated commands..."); - let compiler = cache.compiler; - let mut commands = cache.generated_commands; - let rc_cache = Rc::new(RefCell::new(cache)); + let commands = Rc::new(RefCell::new(cache.generated_commands.clone())); - for sys_module in &commands.system_modules { + let general_args = &cache.generated_commands.general_args.get_args(); + for sys_module in &cache.generated_commands.system_modules { // TODO: will be deprecated soon, hopefully - execute_command(compiler, program_data, sys_module.1, &rc_cache.borrow())?; + execute_command(program_data, sys_module.1, cache.get_process_env_args())?; } - let general_args = commands.general_args.get_args(); - - let translation_units = commands - .get_all_command_lines() - .filter(|scl| scl.need_to_build) - .collect::>(); - - log::warn!("Detected lines: {:?}", translation_units); - - for translation_unit_cmd in translation_units { - let translation_unit_cmd_args: Arguments = general_args - .iter() - .chain(translation_unit_cmd.args.iter()) - .collect(); - - let r = execute_command( - compiler, - program_data, - &translation_unit_cmd_args, - &rc_cache.borrow(), - ); - translation_unit_cmd.execution_result = CommandExecutionResult::from(&r); - - if let Err(e) = r { - cache::save(program_data, &mut cache, commands, test_mode)?; - return Err(e); - } else if !r.as_ref().unwrap().success() { - let err = eyre!( - "Ending the program, because the build of: {:?} failed", - translation_unit_cmd.filename + { + // Scopes for avoiding 'already borrow' errors on the different processes + // TODO: better move it to a custom procedure + let mut commands_borrow = (*commands).borrow_mut(); + let translation_units = commands_borrow + .get_all_command_lines() + .filter(|scl| scl.need_to_build) + .collect::>(); + + for translation_unit_cmd in translation_units { + let translation_unit_cmd_args: Arguments = general_args + .iter() + .chain(translation_unit_cmd.args.iter()) + .collect(); + + let r = execute_command( + program_data, + &translation_unit_cmd_args, + cache.get_process_env_args(), ); - cache::save(program_data, &mut cache, commands, test_mode)?; - return Err(err); + translation_unit_cmd.execution_result = CommandExecutionResult::from(&r); // TODO: we are modifying the cloned :( + + if let Err(e) = r { + cache::save2(program_data, cache, test_mode)?; + return Err(e); + } else if !r.as_ref().unwrap().success() { + let err = eyre!( + "Ending the program, because the build of: {:?} failed", + translation_unit_cmd.filename + ); + cache::save2(program_data, cache, test_mode)?; + return Err(err); + } } } - if !commands.linker.args.is_empty() { + if !cache.generated_commands.linker.args.is_empty() { log::debug!("Processing the linker command line..."); let r = execute_command( - compiler, program_data, - &commands.linker.args, - &rc_cache.borrow(), + &cache.generated_commands.linker.args, + cache.get_process_env_args(), ); - commands.linker.execution_result = CommandExecutionResult::from(&r); + commands.borrow_mut().linker.execution_result = CommandExecutionResult::from(&r); if let Err(e) = r { - cache::save(program_data, &mut cache, commands, test_mode)?; + cache::save2(program_data, cache, test_mode)?; return Err(e); } else if !r.as_ref().unwrap().success() { - cache::save(program_data, &mut cache, commands, test_mode)?; + cache::save2(program_data, cache, test_mode)?; return Err(eyre!( "Ending the program, because the linker command line execution failed", )); } } - cache::save(program_data, rc_cache.borrow_mut(), commands, test_mode)?; + let updated_commands = (*commands).borrow_mut().clone(); + cache.generated_commands = updated_commands; // TODO: we do really need another way of do this (we needed two clones) + cache::save2(program_data, cache, test_mode)?; Ok(CommandExecutionResult::Success) } +// fn execute_linker_command_line(program_data: &ZorkModel, +// // commands: &mut Commands, +// commands: Rc>, +// env_vars: &EnvVars) -> Result<()> { +// // if !commands.linker.args.is_empty() { +// log::debug!("Processing the linker command line..."); +// +// let r = execute_command( +// program_data, +// &commands.linker.args, +// env_vars, +// ); +// commands.linker.execution_result = CommandExecutionResult::from(&r); +// +// if let Err(e) = r { +// // cache::save(program_data, &mut cache, test_mode)?; +// return Err(e); +// } else if !r.as_ref().unwrap().success() { +// // cache::save(program_data, &mut cache, test_mode)?; +// return Err(eyre!( +// "Ending the program, because the linker command line execution failed", +// )); +// } else { Ok(()) } +// // } +// } + /// Executes a new [`std::process::Command`] to run the generated binary /// after the build process in the specified shell pub fn autorun_generated_binary( @@ -141,15 +164,16 @@ pub fn autorun_generated_binary( /// Executes a new [`std::process::Command`] configured according the chosen /// compiler and the current operating system fn execute_command( - compiler: CppCompiler, model: &ZorkModel, arguments: T, - cache: &ZorkCache, + // cache: &ZorkCache, + env_vars: &EnvVars, ) -> Result where T: IntoIterator + std::fmt::Display + std::marker::Copy, S: AsRef, { + let compiler = model.compiler.cpp_compiler; log::trace!( "[{compiler}] - Executing command => {:?}", format!("{} {arguments}", compiler.get_driver(&model.compiler),) @@ -159,37 +183,12 @@ where let os_driver = OsStr::new(driver.as_ref()); std::process::Command::new(os_driver) .args(arguments) - .envs(cache.get_process_env_args()) + .envs(env_vars) .spawn()? .wait() .with_context(|| format!("[{compiler}] - Command {arguments} failed!")) } -fn execute_command2( - compiler: CppCompiler, - model: &ZorkModel, - arguments: &[Argument], - cache: &ZorkCache, -) -> Result { - log::trace!( - "[{compiler}] - Executing command => {:?}", - format!( - "{} {}", - compiler.get_driver(&model.compiler), - arguments.join(" ") - ) - ); - - let driver = compiler.get_driver(&model.compiler); - let os_driver = OsStr::new(driver.as_ref()); - std::process::Command::new(os_driver) - .args(arguments) - .envs(cache.get_process_env_args()) - .spawn()? - .wait() - .with_context(|| format!("[{compiler}] - Command {} failed!", arguments.join(" "))) -} - /// The pieces and details for the generated command line /// for some translation unit /// @@ -298,7 +297,6 @@ pub struct Commands { pub general_args: CommonArgs, // pub compiler_common_args: Box, - pub interfaces: Vec, pub implementations: Vec, pub sources: Vec, @@ -309,7 +307,7 @@ impl Commands { pub fn new( model: &ZorkModel<'_>, general_args: CommonArgs, - compiler_specific_common_args: Box, + _compiler_specific_common_args: Box, ) -> Self { Self { // TODO: try to see if its possible to move around the code and have a From, avoiding default initialization, @@ -322,7 +320,6 @@ impl Commands { general_args, // compiler_common_args: compiler_specific_common_args, - system_modules: HashMap::with_capacity(0), interfaces: Vec::with_capacity(0), implementations: Vec::with_capacity(0), diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 625270da..a5d72e79 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -16,7 +16,7 @@ use crate::project_model::compiler::StdLibMode; use crate::utils::constants; use crate::{ cache::ZorkCache, - cli::output::{arguments::Argument, commands::Commands}, + cli::output::arguments::Argument, project_model::{ compiler::CppCompiler, modules::{ModuleImplementationModel, ModuleInterfaceModel}, @@ -34,14 +34,14 @@ pub fn build_project<'a>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache, tests: bool, -) -> Result { +) -> Result<()> { // Generate the Flyweight struct, with the repetitive data and details of the command lines - let general_args = CommonArgs::from(model); - let compiler_specific_common_args: Box = - data_factory::compiler_common_arguments_factory(model); + let _general_args = CommonArgs::from(model); + let _compiler_specific_common_args: Box = + data_factory::compiler_common_arguments_factory(model); // TODO: guard it with a presence check // A registry of the generated command lines - let mut commands = Commands::new(model, general_args, compiler_specific_common_args); + // let commands = Commands::new(model, general_args, compiler_specific_common_args); // TODO from cache, and find them here instead from the cache // TODO: add them to the commands DS, so they are together until they're generated @@ -52,17 +52,17 @@ pub fn build_project<'a>( if let Some(modules) = &model.modules { // Pre-tasks if model.compiler.cpp_compiler == CppCompiler::GCC && !modules.sys_modules.is_empty() { - helpers::build_sys_modules(model, &mut commands, cache) + helpers::build_sys_modules(model, cache) } - process_modules(model, cache, &mut commands)?; + process_modules(model, cache)?; }; // 2nd - Build the non module sources - build_sources(model, cache, &mut commands, tests)?; + build_sources(model, cache, tests)?; // 3rd - Build the executable or the tests - build_executable(model, cache, &mut commands, tests)?; + build_executable(model, cache, tests)?; - Ok(commands) + Ok(()) } /// Builds the C++ standard library as a pre-step acording to the specification @@ -76,7 +76,10 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache) { let built_stdlib_path = &cache.compilers_metadata.msvc.stdlib_bmi_path; if !built_stdlib_path.exists() { - log::trace!("Building the {:?} C++ standard library implementation", compiler); + log::trace!( + "Building the {:?} C++ standard library implementation", + compiler + ); let cpp_stdlib = msvc_args::generate_std_cmd(model, cache, StdLibMode::Cpp); cache.generated_commands.cpp_stdlib = Some(cpp_stdlib); } @@ -93,28 +96,18 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache) { /// Triggers the build process for compile the source files declared for the project /// If this flow is enabled by the Cli arg `Tests`, then the executable will be generated /// for the files and properties declared for the tests section in the configuration file -fn build_executable( - model: &ZorkModel<'_>, - cache: &ZorkCache, - commands: &'_ mut Commands, - tests: bool, -) -> Result<()> { +fn build_executable(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> Result<()> { // TODO: Check if the command line is the same as the previous? If there's no new sources? // And avoid re-executing? // TODO refactor this code, just having the if-else branch inside the fn if tests { - generate_main_command_line_args(model, cache, commands, &model.tests) + generate_main_command_line_args(model, cache, &model.tests) } else { - generate_main_command_line_args(model, cache, commands, &model.executable) + generate_main_command_line_args(model, cache, &model.executable) } } -fn build_sources( - model: &ZorkModel<'_>, - cache: &ZorkCache, - commands: &'_ mut Commands, - tests: bool, -) -> Result<()> { +fn build_sources(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> Result<()> { log::info!("Generating the commands for the source files..."); let srcs = if tests { &model.tests.sourceset.sources @@ -123,15 +116,15 @@ fn build_sources( }; srcs.iter().for_each(|src| if !flag_source_file_without_changes(&model.compiler.cpp_compiler, cache, &src.file()) { - sources::generate_sources_arguments(model, commands, cache, &model.tests, src); + sources::generate_sources_arguments(model, cache, &model.tests, src); } else { let command_line = SourceCommandLine::from_translation_unit( src, Arguments::default(), true, CommandExecutionResult::Cached, ); log::trace!("Source file: {:?} was not modified since the last iteration. No need to rebuilt it again.", &src.file()); - commands.sources.push(command_line); - commands.add_linker_file_path_owned(helpers::generate_obj_file_path( + cache.generated_commands.sources.push(command_line); + cache.generated_commands.add_linker_file_path_owned(helpers::generate_obj_file_path( model.compiler.cpp_compiler, &model.build.output_dir, src, )) }); @@ -144,25 +137,15 @@ fn build_sources( /// This function acts like a operation result processor, by running instances /// and parsing the obtained result, handling the flux according to the /// compiler responses -fn process_modules( - model: &ZorkModel, - cache: &mut ZorkCache, - commands: &mut Commands, -) -> Result<()> { +fn process_modules(model: &ZorkModel, cache: &mut ZorkCache) -> Result<()> { log::info!("Generating the commands for the module interfaces and partitions..."); - process_module_interfaces( - model, - cache, - &model.modules.as_ref().unwrap().interfaces, - commands, - ); + process_module_interfaces(model, cache, &model.modules.as_ref().unwrap().interfaces); log::info!("Generating the commands for the module implementations and partitions..."); process_module_implementations( model, cache, &model.modules.as_ref().unwrap().implementations, - commands, ); Ok(()) @@ -174,7 +157,6 @@ fn process_module_interfaces<'a>( model: &'a ZorkModel<'_>, cache: &mut ZorkCache, interfaces: &'a [ModuleInterfaceModel], - commands: &mut Commands, ) { interfaces.iter().for_each(|module_interface| { let compiler = cache.compiler; @@ -188,16 +170,16 @@ fn process_module_interfaces<'a>( } let mut cached_cmd_line = generated_cmd.clone(); // TODO: somehow, we should manage to solve this on the future cached_cmd_line.need_to_build = translation_unit_must_be_rebuilt; - commands.linker.add_owned_buildable_at(helpers::generate_prebuilt_miu( // TODO: extremely provisional + cache.generated_commands.linker.add_owned_buildable_at(helpers::generate_prebuilt_miu( // TODO: extremely provisional model.compiler.cpp_compiler, &model.build.output_dir, module_interface, )); cached_cmd_line } else { - sources::generate_module_interface_cmd(model, cache, module_interface, commands) + sources::generate_module_interface_cmd(model, cache, module_interface) }; // TODO: should we get rid of commands and just store everything on the Cache? - commands.interfaces.push(command_line); + cache.generated_commands.interfaces.push(command_line); // commands.linker.add_owned_buildable_at(helpers::generate_prebuilt_miu( // model.compiler.cpp_compiler, &model.build.output_dir, module_interface // )) @@ -207,9 +189,8 @@ fn process_module_interfaces<'a>( /// Generates the commands for every [`ModuleImplementationModel`] fn process_module_implementations<'a>( model: &'a ZorkModel, - cache: &ZorkCache, + cache: &mut ZorkCache, impls: &'a [ModuleImplementationModel], - commands: &mut Commands, ) { impls.iter().for_each(|module_impl| { let compiler = cache.compiler; @@ -222,24 +203,24 @@ fn process_module_implementations<'a>( log::trace!("Source file: {:?} was not modified since the last iteration. No need to rebuilt it again.", &module_impl.file()); } let mut cached_cmd_line = generated_cmd.clone(); // TODO: somehow, we should manage to solve this on the future + // TODO: just get_mut on source command line cached_cmd_line.need_to_build = translation_unit_must_be_rebuilt; - commands.linker.add_owned_buildable_at(helpers::generate_impl_obj_file( // TODO: extremely provisional + cache.generated_commands.linker.add_owned_buildable_at(helpers::generate_impl_obj_file( // TODO: extremely provisional model.compiler.cpp_compiler, &model.build.output_dir, module_impl, )); cached_cmd_line } else { - sources::generate_module_implementation_cmd(model, cache, module_impl, commands) + sources::generate_module_implementation_cmd(model, cache, module_impl) }; - commands.interfaces.push(command_line); + cache.generated_commands.interfaces.push(command_line); }); } /// Generates the command line arguments for the desired target pub fn generate_main_command_line_args<'a>( model: &'a ZorkModel, - cache: &ZorkCache, - commands: &mut Commands, + cache: &mut ZorkCache, target: &'a impl ExecutableTarget<'a>, ) -> Result<()> { log::info!("Generating the main command line..."); @@ -318,10 +299,17 @@ pub fn generate_main_command_line_args<'a>( } }; - arguments.extend(commands.linker.built_files.iter().map(Argument::from)); // TODO can't we avoid this, and just add the pathbufs? + arguments.extend( + cache + .generated_commands + .linker + .built_files + .iter() + .map(Argument::from), + ); // TODO can't we avoid this, and just add the pathbufs? - commands.linker.args.extend(arguments); - commands.linker.built_files = target // TODO: built_files means raw cpp sources + cache.generated_commands.linker.args.extend(arguments); + cache.generated_commands.linker.built_files = target // TODO: built_files means raw cpp sources // TODO: add a custom collector on the mod sources // TODO: just name the field 'sources' .sourceset() @@ -346,7 +334,7 @@ mod sources { bounds::{ExecutableTarget, TranslationUnit}, cli::output::{ arguments::clang_args, - commands::{CommandExecutionResult, Commands, SourceCommandLine}, + commands::{CommandExecutionResult, SourceCommandLine}, }, project_model::{ compiler::CppCompiler, @@ -358,8 +346,7 @@ mod sources { /// Generates the command line arguments for non-module source files pub fn generate_sources_arguments<'a>( model: &'a ZorkModel, - commands: &mut Commands, - cache: &ZorkCache, + cache: &mut ZorkCache, target: &'a impl ExecutableTarget<'a>, source: &'a SourceFile, ) { @@ -424,16 +411,17 @@ mod sources { false, CommandExecutionResult::default(), ); - commands.sources.push(command_line); - commands.add_linker_file_path_owned(obj_file) + cache.generated_commands.sources.push(command_line); + cache + .generated_commands + .add_linker_file_path_owned(obj_file) } /// Generates the expected arguments for precompile the BMIs depending on self pub fn generate_module_interface_cmd<'a>( model: &'a ZorkModel, - cache: &ZorkCache, + cache: &mut ZorkCache, interface: &'a ModuleInterfaceModel, - commands: &mut Commands, ) -> SourceCommandLine { let compiler = model.compiler.cpp_compiler; let out_dir: &Path = model.build.output_dir.as_ref(); @@ -466,7 +454,9 @@ mod sources { // The output file let obj_file = helpers::generate_prebuilt_miu(compiler, out_dir, interface); arguments.create_and_push(&obj_file); - commands.add_linker_file_path_owned(obj_file); + cache + .generated_commands + .add_linker_file_path_owned(obj_file); // The input file arguments.create_and_push(interface.file()); } @@ -497,7 +487,9 @@ mod sources { // The output .obj file let obj_file = helpers::generate_prebuilt_miu(compiler, out_dir, interface); arguments.create_and_push(format!("/Fo{}", obj_file.display())); - commands.add_linker_file_path_owned(obj_file); + cache + .generated_commands + .add_linker_file_path_owned(obj_file); if let Some(partition) = &interface.partition { if partition.is_internal_partition { @@ -523,7 +515,9 @@ mod sources { arguments.create_and_push("-o"); let obj_file = helpers::generate_prebuilt_miu(compiler, out_dir, interface); arguments.create_and_push(&obj_file); - commands.add_linker_file_path_owned(obj_file); + cache + .generated_commands + .add_linker_file_path_owned(obj_file); } } @@ -533,9 +527,8 @@ mod sources { /// Generates the expected arguments for compile the implementation module files pub fn generate_module_implementation_cmd<'a>( model: &'a ZorkModel, - cache: &ZorkCache, + cache: &mut ZorkCache, implementation: &'a ModuleImplementationModel, - commands: &mut Commands, ) -> SourceCommandLine { let compiler = model.compiler.cpp_compiler; let out_dir = model.build.output_dir.as_ref(); @@ -559,7 +552,9 @@ mod sources { out_dir, implementation, ); - commands.add_linker_file_path(&obj_file_path); + cache + .generated_commands + .add_linker_file_path(&obj_file_path); arguments.create_and_push(obj_file_path); clang_args::add_direct_module_interfaces_dependencies( @@ -601,7 +596,9 @@ mod sources { .join::<&str>(&implementation.file_stem()) .with_extension(compiler.get_obj_file_extension()); - commands.add_linker_file_path(&obj_file_path); + cache + .generated_commands + .add_linker_file_path(&obj_file_path); arguments.create_and_push(format!("/Fo{}", obj_file_path.display())); } CppCompiler::GCC => { @@ -613,7 +610,9 @@ mod sources { arguments.create_and_push("-o"); let obj_file_path = helpers::generate_impl_obj_file(compiler, out_dir, implementation); - commands.add_linker_file_path(&obj_file_path); + cache + .generated_commands + .add_linker_file_path(&obj_file_path); arguments.create_and_push(obj_file_path); } } @@ -702,7 +701,7 @@ mod helpers { /// /// This is for `GCC` and `Clang` /// TODO: With the inclusion of std named modules, want we to support this anymore? - pub(crate) fn build_sys_modules(model: &ZorkModel, commands: &mut Commands, cache: &ZorkCache) { + pub(crate) fn build_sys_modules(model: &ZorkModel, cache: &mut ZorkCache) { if !cache.compilers_metadata.system_modules.is_empty() { // TODO BUG - this is not correct. // If user later adds a new module, it won't be processed @@ -766,7 +765,7 @@ mod helpers { // Newest TODO: Can we just store them as Argument(s) in an Arguments? For example, with // the new pre-tasks (and therefore, being cached in an unified way?) for collection_args in sys_modules { - commands.system_modules.insert( + cache.generated_commands.system_modules.insert( // [3] is for the 4th flag pushed to v collection_args[3].value().to_string(), Arguments::from_vec(collection_args), diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 3b26fcb6..836a965c 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -16,13 +16,13 @@ pub mod utils; /// data sent to stdout/stderr pub mod worker { use crate::{config_file, utils::fs::get_project_root_absolute_path}; - use std::{cell::RefCell, fs, path::Path, rc::Rc}; + use std::{fs, path::Path}; use crate::{ cache::{self, ZorkCache}, cli::{ input::{CliArgs, Command}, - output::commands::{self, CommandExecutionResult, Commands}, + output::commands::{self, CommandExecutionResult}, }, compiler::build_project, project_model::{compiler::CppCompiler, ZorkModel}, @@ -110,13 +110,10 @@ pub mod worker { mut cache: ZorkCache, ) -> Result { // TODO: the return type isn't as clever as it could be - let commands: Commands; - - let rc_cache = Rc::new(RefCell::new(cache.clone())); // TODO: provisional clone match cli_args.command { Command::Build => { - commands = build_project(program_data, &mut cache, false) + build_project(program_data, &mut cache, false) .with_context(|| "Failed to build project")?; commands::run_generated_commands( @@ -127,12 +124,15 @@ pub mod worker { ) } Command::Run => { - commands = build_project(program_data, &mut cache, false) + build_project(program_data, &mut cache, false) .with_context(|| "Failed to build project")?; - match commands::run_generated_commands(program_data, - // commands, - cache, false) { + match commands::run_generated_commands( + program_data, + // commands, + cache, + false, + ) { Ok(_) => commands::autorun_generated_binary( &program_data.compiler.cpp_compiler, &program_data.build.output_dir, @@ -142,12 +142,15 @@ pub mod worker { } } Command::Test => { - commands = build_project(program_data, &mut cache, true) + build_project(program_data, &mut cache, true) .with_context(|| "Failed to build project")?; - match commands::run_generated_commands(program_data, - // commands, - cache, true) { + match commands::run_generated_commands( + program_data, + // commands, + cache, + true, + ) { Ok(_) => commands::autorun_generated_binary( &program_data.compiler.cpp_compiler, &program_data.build.output_dir, diff --git a/zork++/src/lib/utils/fs.rs b/zork++/src/lib/utils/fs.rs index a7b838f1..fb89ae0a 100644 --- a/zork++/src/lib/utils/fs.rs +++ b/zork++/src/lib/utils/fs.rs @@ -87,7 +87,7 @@ pub fn get_file_details>(p: P) -> Result<(PathBuf, String, String pub fn serialize_object_to_file(path: &Path, data: &T) -> Result<()> where - T: Serialize, + T: Serialize + ?Sized, { serde_json::to_writer_pretty( File::create(path).with_context(|| "Error creating the cache file")?, From 62ebe6ae15f055e4300a941a0edf30b78bd03d17 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sat, 29 Jun 2024 11:32:06 +0200 Subject: [PATCH 18/73] chore(wip): Towards the unique generation and generification of the processing of translation units --- zork++/src/lib/cache/mod.rs | 12 ++-- zork++/src/lib/compiler/mod.rs | 110 +++++++++++++++------------------ 2 files changed, 57 insertions(+), 65 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 91f593b4..09f26d33 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -130,22 +130,22 @@ impl<'a> ZorkCache<'a> { &self.last_program_execution } pub fn get_module_ifc_cmd( - &self, + &mut self, module_interface_model: &ModuleInterfaceModel, - ) -> Option<&SourceCommandLine> { + ) -> Option<&mut SourceCommandLine> { self.generated_commands .interfaces - .iter() + .iter_mut() .find(|mi| module_interface_model.file() == (*mi).path()) } pub fn get_module_impl_cmd( - &self, + &mut self, module_impl_model: &ModuleImplementationModel, - ) -> Option<&SourceCommandLine> { + ) -> Option<&mut SourceCommandLine> { self.generated_commands .implementations - .iter() + .iter_mut() .find(|mi| module_impl_model.file() == (*mi).path()) } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index a5d72e79..822304e2 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -10,7 +10,7 @@ use std::path::Path; use crate::bounds::{ExecutableTarget, ExtraArgs, TranslationUnit}; use crate::cli::output::arguments::{clang_args, msvc_args, Arguments}; -use crate::cli::output::commands::{CommandExecutionResult, SourceCommandLine}; +use crate::cli::output::commands::SourceCommandLine; use crate::compiler::helpers::flag_source_file_without_changes; use crate::project_model::compiler::StdLibMode; use crate::utils::constants; @@ -76,7 +76,7 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache) { let built_stdlib_path = &cache.compilers_metadata.msvc.stdlib_bmi_path; if !built_stdlib_path.exists() { - log::trace!( + log::info!( "Building the {:?} C++ standard library implementation", compiler ); @@ -86,7 +86,7 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache) { let built_stdlib_compat_path = &cache.compilers_metadata.msvc.c_stdlib_bmi_path; if !built_stdlib_compat_path.exists() { - log::trace!("Building the {:?} C compat CPP std lib", compiler); + log::info!("Building the {:?} C compat CPP std lib", compiler); let c_compat = msvc_args::generate_std_cmd(model, cache, StdLibMode::CCompat); cache.generated_commands.c_compat_stdlib = Some(c_compat); } @@ -115,38 +115,45 @@ fn build_sources(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> R &model.executable.sourceset.sources }; - srcs.iter().for_each(|src| if !flag_source_file_without_changes(&model.compiler.cpp_compiler, cache, &src.file()) { - sources::generate_sources_arguments(model, cache, &model.tests, src); - } else { - let command_line = SourceCommandLine::from_translation_unit( - src, Arguments::default(), true, CommandExecutionResult::Cached, - ); - - log::trace!("Source file: {:?} was not modified since the last iteration. No need to rebuilt it again.", &src.file()); - cache.generated_commands.sources.push(command_line); - cache.generated_commands.add_linker_file_path_owned(helpers::generate_obj_file_path( - model.compiler.cpp_compiler, &model.build.output_dir, src, - )) - }); + srcs.iter().for_each( + |src| { + if !flag_source_file_without_changes(&model.compiler.cpp_compiler, cache, &src.file()) { + sources::generate_sources_arguments(model, cache, &model.tests, src); + } + }, // else { + // let command_line = SourceCommandLine::from_translation_unit( + // src, Arguments::default(), true, CommandExecutionResult::Cached, + // ); + // + // log::trace!("Source file: {:?} was not modified since the last iteration. No need to rebuilt it again.", &src.file()); + // cache.generated_commands.sources.push(command_line); + // cache.generated_commands.add_linker_file_path_owned(helpers::generate_obj_file_path( + // model.compiler.cpp_compiler, &model.build.output_dir, src, + // )) + // } + ); Ok(()) } -/// Triggers the build process for compile the declared modules in the project -/// -/// This function acts like a operation result processor, by running instances -/// and parsing the obtained result, handling the flux according to the -/// compiler responses +/// Generates the commands for the C++ modules declared in the project fn process_modules(model: &ZorkModel, cache: &mut ZorkCache) -> Result<()> { log::info!("Generating the commands for the module interfaces and partitions..."); - process_module_interfaces(model, cache, &model.modules.as_ref().unwrap().interfaces); + + let modules = model.modules.as_ref().unwrap(); // TODO: review this opt again + + process_module_interfaces(model, cache, &modules.interfaces); + + // TODO: find a way to avoid duplicating the implementations of both kind of translation units commands generation, since they + // only differs on the call that are generated + // Alternatives: self as any?, maybe a translation unit kind? knowing the call, primitive cast of + // an slice of trait objects? + + // TODO: make a trait exclusive for ModuleTranslationUnit: TranslationUnit, since the operations required + // are mostly paths and add dependencies -> &[Cow<'_, str>] log::info!("Generating the commands for the module implementations and partitions..."); - process_module_implementations( - model, - cache, - &model.modules.as_ref().unwrap().implementations, - ); + process_module_implementations(model, cache, &modules.implementations); Ok(()) } @@ -158,31 +165,22 @@ fn process_module_interfaces<'a>( cache: &mut ZorkCache, interfaces: &'a [ModuleInterfaceModel], ) { + // let c: Vec<&dyn TranslationUnit> = interfaces.iter().map(|mi| mi as &dyn TranslationUnit).collect(); + // let d = c.iter().map(|tu| tu as &dyn ModuleInterfaceModel).collect(); // downcast, as isn't usable for non primitives + let lpe = cache.last_program_execution; interfaces.iter().for_each(|module_interface| { let compiler = cache.compiler; - let lpe = cache.last_program_execution(); - let command_line = if let Some(generated_cmd) = cache.get_module_ifc_cmd(module_interface) { - let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, lpe, generated_cmd, &module_interface.file()); + if let Some(generated_cmd) = cache.get_module_ifc_cmd(module_interface) { + let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, &lpe, generated_cmd, &module_interface.file()); log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_interface.file()); if !translation_unit_must_be_rebuilt { log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &module_interface.file()); } - let mut cached_cmd_line = generated_cmd.clone(); // TODO: somehow, we should manage to solve this on the future - cached_cmd_line.need_to_build = translation_unit_must_be_rebuilt; - cache.generated_commands.linker.add_owned_buildable_at(helpers::generate_prebuilt_miu( // TODO: extremely provisional - model.compiler.cpp_compiler, &model.build.output_dir, module_interface, - )); - cached_cmd_line + generated_cmd.need_to_build = translation_unit_must_be_rebuilt; } else { sources::generate_module_interface_cmd(model, cache, module_interface) }; - - // TODO: should we get rid of commands and just store everything on the Cache? - cache.generated_commands.interfaces.push(command_line); - // commands.linker.add_owned_buildable_at(helpers::generate_prebuilt_miu( - // model.compiler.cpp_compiler, &model.build.output_dir, module_interface - // )) }); } @@ -192,28 +190,20 @@ fn process_module_implementations<'a>( cache: &mut ZorkCache, impls: &'a [ModuleImplementationModel], ) { + let lpe = cache.last_program_execution; // TODO: check for an Rc< or other solution, but closure below requires unique access impls.iter().for_each(|module_impl| { let compiler = cache.compiler; - let lpe = cache.last_program_execution(); - let command_line = if let Some(generated_cmd) = cache.get_module_impl_cmd(module_impl) { - let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, lpe, generated_cmd, &module_impl.file()); + if let Some(generated_cmd) = cache.get_module_impl_cmd(module_impl) { + let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, &lpe, generated_cmd, &module_impl.file()); log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_impl.file()); if !translation_unit_must_be_rebuilt { log::trace!("Source file: {:?} was not modified since the last iteration. No need to rebuilt it again.", &module_impl.file()); } - let mut cached_cmd_line = generated_cmd.clone(); // TODO: somehow, we should manage to solve this on the future - // TODO: just get_mut on source command line - cached_cmd_line.need_to_build = translation_unit_must_be_rebuilt; - cache.generated_commands.linker.add_owned_buildable_at(helpers::generate_impl_obj_file( // TODO: extremely provisional - model.compiler.cpp_compiler, &model.build.output_dir, module_impl, - )); - cached_cmd_line + generated_cmd.need_to_build = translation_unit_must_be_rebuilt; } else { sources::generate_module_implementation_cmd(model, cache, module_impl) }; - - cache.generated_commands.interfaces.push(command_line); }); } @@ -420,9 +410,9 @@ mod sources { /// Generates the expected arguments for precompile the BMIs depending on self pub fn generate_module_interface_cmd<'a>( model: &'a ZorkModel, - cache: &mut ZorkCache, + cache: &'a mut ZorkCache, interface: &'a ModuleInterfaceModel, - ) -> SourceCommandLine { + ) { let compiler = model.compiler.cpp_compiler; let out_dir: &Path = model.build.output_dir.as_ref(); @@ -521,7 +511,8 @@ mod sources { } } - SourceCommandLine::for_translation_unit(interface, arguments) + let cmd_line = SourceCommandLine::for_translation_unit(interface, arguments); + cache.generated_commands.interfaces.push(cmd_line); } /// Generates the expected arguments for compile the implementation module files @@ -529,7 +520,7 @@ mod sources { model: &'a ZorkModel, cache: &mut ZorkCache, implementation: &'a ModuleImplementationModel, - ) -> SourceCommandLine { + ) { let compiler = model.compiler.cpp_compiler; let out_dir = model.build.output_dir.as_ref(); @@ -617,7 +608,8 @@ mod sources { } } - SourceCommandLine::for_translation_unit(implementation, arguments) + let cmd = SourceCommandLine::for_translation_unit(implementation, arguments); + cache.generated_commands.implementations.push(cmd); } } From 118633a35407f341a74379ff61728a66bd52f44f Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 30 Jun 2024 11:37:19 +0200 Subject: [PATCH 19/73] feat: No ReferenceCounted or Interior Mutability patterns are required anymore. Everything works again with plain references or moved values --- zork++/src/lib/cli/output/commands.rs | 72 ++++++++++----------------- zork++/src/lib/compiler/mod.rs | 33 ++++-------- zork++/src/lib/lib.rs | 57 ++++++--------------- 3 files changed, 50 insertions(+), 112 deletions(-) diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index feecaad1..b85c9cbe 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -1,11 +1,9 @@ //! Contains helpers and data structures to be processed in a nice and neat way the commands generated to be executed //! by Zork++ -use std::cell::RefCell; use std::collections::HashMap; use std::ffi::OsStr; use std::fmt::Debug; -use std::rc::Rc; use std::slice::Iter; use std::{ path::{Path, PathBuf}, @@ -18,7 +16,7 @@ use crate::cache::EnvVars; use crate::cli::output::arguments::Arguments; use crate::compiler::data_factory::{CommonArgs, CompilerCommonArguments}; use crate::{ - cache::{self, ZorkCache}, + cache::ZorkCache, project_model::{compiler::CppCompiler, ZorkModel}, utils::constants, }; @@ -30,29 +28,32 @@ use serde::{Deserialize, Serialize}; pub fn run_generated_commands( program_data: &ZorkModel<'_>, - mut cache: ZorkCache<'_>, - test_mode: bool, + cache: &mut ZorkCache<'_>, ) -> Result { log::info!("Proceeding to execute the generated commands..."); - let commands = Rc::new(RefCell::new(cache.generated_commands.clone())); - let general_args = &cache.generated_commands.general_args.get_args(); + let env_args = cache.get_process_env_args().clone(); // TODO: this is yet better than clone the + // generated commands (maybe) but I'm not + // happy with it + for sys_module in &cache.generated_commands.system_modules { // TODO: will be deprecated soon, hopefully - execute_command(program_data, sys_module.1, cache.get_process_env_args())?; + // But while isn't deleted, we could normalize them into SourceCommandLine + execute_command(program_data, sys_module.1, &env_args)?; } { // Scopes for avoiding 'already borrow' errors on the different processes // TODO: better move it to a custom procedure - let mut commands_borrow = (*commands).borrow_mut(); - let translation_units = commands_borrow + let translation_units = cache + .generated_commands .get_all_command_lines() .filter(|scl| scl.need_to_build) .collect::>(); for translation_unit_cmd in translation_units { + // Join the concrete args of any translation unit with the general flyweights let translation_unit_cmd_args: Arguments = general_args .iter() .chain(translation_unit_cmd.args.iter()) @@ -61,19 +62,17 @@ pub fn run_generated_commands( let r = execute_command( program_data, &translation_unit_cmd_args, - cache.get_process_env_args(), + &env_args, ); - translation_unit_cmd.execution_result = CommandExecutionResult::from(&r); // TODO: we are modifying the cloned :( + translation_unit_cmd.execution_result = CommandExecutionResult::from(&r); if let Err(e) = r { - cache::save2(program_data, cache, test_mode)?; return Err(e); } else if !r.as_ref().unwrap().success() { let err = eyre!( "Ending the program, because the build of: {:?} failed", translation_unit_cmd.filename ); - cache::save2(program_data, cache, test_mode)?; return Err(err); } } @@ -85,53 +84,27 @@ pub fn run_generated_commands( let r = execute_command( program_data, &cache.generated_commands.linker.args, - cache.get_process_env_args(), + &env_args, ); - commands.borrow_mut().linker.execution_result = CommandExecutionResult::from(&r); + cache.generated_commands.linker.execution_result = CommandExecutionResult::from(&r); if let Err(e) = r { - cache::save2(program_data, cache, test_mode)?; + // cache::save2(program_data, cache, test_mode)?; return Err(e); } else if !r.as_ref().unwrap().success() { - cache::save2(program_data, cache, test_mode)?; + // cache::save2(program_data, cache, test_mode)?; return Err(eyre!( "Ending the program, because the linker command line execution failed", )); } } - let updated_commands = (*commands).borrow_mut().clone(); - cache.generated_commands = updated_commands; // TODO: we do really need another way of do this (we needed two clones) - cache::save2(program_data, cache, test_mode)?; + /* let updated_commands = (*commands).borrow_mut().clone(); + cache.generated_commands = updated_commands; // TODO: we do really need another way of do this (we needed two clones) */ + // cache::save2(program_data, cache, test_mode)?; Ok(CommandExecutionResult::Success) } -// fn execute_linker_command_line(program_data: &ZorkModel, -// // commands: &mut Commands, -// commands: Rc>, -// env_vars: &EnvVars) -> Result<()> { -// // if !commands.linker.args.is_empty() { -// log::debug!("Processing the linker command line..."); -// -// let r = execute_command( -// program_data, -// &commands.linker.args, -// env_vars, -// ); -// commands.linker.execution_result = CommandExecutionResult::from(&r); -// -// if let Err(e) = r { -// // cache::save(program_data, &mut cache, test_mode)?; -// return Err(e); -// } else if !r.as_ref().unwrap().success() { -// // cache::save(program_data, &mut cache, test_mode)?; -// return Err(eyre!( -// "Ending the program, because the linker command line execution failed", -// )); -// } else { Ok(()) } -// // } -// } - /// Executes a new [`std::process::Command`] to run the generated binary /// after the build process in the specified shell pub fn autorun_generated_binary( @@ -328,6 +301,11 @@ impl Commands { } } + /// Returns an [std::iter::Chain] (behind the opaque impl clause return type signature) + /// which points to all the generated commmands for the two variants of the compilers vendors C++ modular + /// standard libraries implementations (see: [crate::project_model::compiler::StdLibMode]) + /// joined to all the commands generated for every [TranslationUnit] declared by the user for + /// its project pub fn get_all_command_lines( &mut self, ) -> impl Iterator + Debug + '_ { diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 822304e2..15d69a0c 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -26,11 +26,9 @@ use crate::{ use self::data_factory::{CommonArgs, CompilerCommonArguments}; -/// The entry point of the compilation process -/// -/// Whenever this process gets triggered, the files declared within the -/// configuration file will be build -pub fn build_project<'a>( +/// The core procedure. Generates the commands that will be sent to the compiler +/// for every translation unit declared by the user for its project +pub fn generate_commands<'a>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache, tests: bool, @@ -40,16 +38,15 @@ pub fn build_project<'a>( let _compiler_specific_common_args: Box = data_factory::compiler_common_arguments_factory(model); // TODO: guard it with a presence check - // A registry of the generated command lines - // let commands = Commands::new(model, general_args, compiler_specific_common_args); - // TODO from cache, and find them here instead from the cache + cache.generated_commands.general_args = _general_args; + // cache. // TODO: add them to the commands DS, so they are together until they're generated // Build the std library as a module build_modular_stdlib(model, cache); // TODO: ward it with an if for only call this fn for the // 1st - Build the modules - if let Some(modules) = &model.modules { + if let Some(modules) = &model.modules { // TODO: re-think again this optional // Pre-tasks if model.compiler.cpp_compiler == CppCompiler::GCC && !modules.sys_modules.is_empty() { helpers::build_sys_modules(model, cache) @@ -57,9 +54,9 @@ pub fn build_project<'a>( process_modules(model, cache)?; }; - // 2nd - Build the non module sources + // 2nd - Generate the commands for the non-module sources build_sources(model, cache, tests)?; - // 3rd - Build the executable or the tests + // 3rd - Build the executable or the tests // TODO: commentary and fn name build_executable(model, cache, tests)?; Ok(()) @@ -116,21 +113,11 @@ fn build_sources(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> R }; srcs.iter().for_each( - |src| { + |src| { // TODO: yet unchanged, we need to replicate the idea on main if !flag_source_file_without_changes(&model.compiler.cpp_compiler, cache, &src.file()) { sources::generate_sources_arguments(model, cache, &model.tests, src); } - }, // else { - // let command_line = SourceCommandLine::from_translation_unit( - // src, Arguments::default(), true, CommandExecutionResult::Cached, - // ); - // - // log::trace!("Source file: {:?} was not modified since the last iteration. No need to rebuilt it again.", &src.file()); - // cache.generated_commands.sources.push(command_line); - // cache.generated_commands.add_linker_file_path_owned(helpers::generate_obj_file_path( - // model.compiler.cpp_compiler, &model.build.output_dir, src, - // )) - // } + } ); Ok(()) diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 836a965c..38c59cd8 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -24,7 +24,7 @@ pub mod worker { input::{CliArgs, Command}, output::commands::{self, CommandExecutionResult}, }, - compiler::build_project, + compiler::generate_commands, project_model::{compiler::CppCompiler, ZorkModel}, utils::{ self, @@ -109,30 +109,17 @@ pub mod worker { program_data: &'a ZorkModel<'_>, mut cache: ZorkCache, ) -> Result { - // TODO: the return type isn't as clever as it could be - match cli_args.command { - Command::Build => { - build_project(program_data, &mut cache, false) - .with_context(|| "Failed to build project")?; + let is_tests_run = cli_args.command.eq(&Command::Test); - commands::run_generated_commands( - program_data, - // commands, - cache, - false, - ) - } - Command::Run => { - build_project(program_data, &mut cache, false) - .with_context(|| "Failed to build project")?; + generate_commands(program_data, &mut cache, false) + .with_context(|| "Failed to generated the commands for the project")?; - match commands::run_generated_commands( - program_data, - // commands, - cache, - false, - ) { + let execution_result = match cli_args.command { + Command::Build => + commands::run_generated_commands( program_data, &mut cache,), + Command::Run | Command::Test => { + match commands::run_generated_commands( program_data, &mut cache,) { Ok(_) => commands::autorun_generated_binary( &program_data.compiler.cpp_compiler, &program_data.build.output_dir, @@ -140,31 +127,17 @@ pub mod worker { ), Err(e) => Err(e), } - } - Command::Test => { - build_project(program_data, &mut cache, true) - .with_context(|| "Failed to build project")?; - - match commands::run_generated_commands( - program_data, - // commands, - cache, - true, - ) { - Ok(_) => commands::autorun_generated_binary( - &program_data.compiler.cpp_compiler, - &program_data.build.output_dir, - &program_data.tests.test_executable_name, - ), - Err(e) => Err(e), - } - } + }, _ => todo!( "This branch should never be reached for now, as do not exists commands that may\ trigger them. The unique remaining, is ::New, that is already processed\ at the very beggining" ), - } + }; + + cache::save2(program_data, cache, is_tests_run)?; + + execution_result } /// Creates the directory for output the elements generated From 8ce930f995b8ac1de20782259516c688cc053bfb Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 30 Jun 2024 12:43:14 +0200 Subject: [PATCH 20/73] feat: Making Clang the first compiler that correctly uses the Flyweights data structures for generating the C++ module interfaces command lines --- zork++/src/lib/cache/mod.rs | 38 +------ zork++/src/lib/cli/output/arguments.rs | 4 +- zork++/src/lib/cli/output/commands.rs | 122 +++++++---------------- zork++/src/lib/compiler/data_factory.rs | 38 ++++++- zork++/src/lib/compiler/mod.rs | 39 +++----- zork++/src/lib/lib.rs | 12 +-- zork++/src/lib/project_model/compiler.rs | 9 ++ 7 files changed, 103 insertions(+), 159 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 09f26d33..f1d5c1bf 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -66,42 +66,8 @@ pub fn load<'a>(program_data: &'a ZorkModel<'_>, cli_args: &CliArgs) -> Result, - mut cache: ZorkCache, - // _commands: Commands, - test_mode: bool, -) -> Result<()> { - let cache_path = &program_data - .build - .output_dir - .join("zork") - .join("cache") - .join(program_data.compiler.cpp_compiler.as_ref()) - .join(constants::ZORK_CACHE_FILENAME); - - // let ro_cache = Rc::clone(&cache); - // let mut cache = cache.borrow_mut(); - cache.run_final_tasks(program_data, test_mode)?; - cache.last_program_execution = Utc::now(); - - // let cache_kind = &(*ro_cache).borrow(); // saves the temporary - serialize_cache(cache_path, &cache.clone()) - // Ok(()) -} - -fn serialize_cache(path: &Path, cache: &ZorkCache) -> Result<()> { - utils::fs::serialize_object_to_file(path, cache) - .with_context(move || "Error saving data to the Zork++ cache") -} - /// Standalone utility for persist the cache to the file system -pub fn save( - program_data: &ZorkModel<'_>, - mut cache: ZorkCache, - // commands: Commands, - test_mode: bool, -) -> Result<()> { +pub fn save2(program_data: &ZorkModel<'_>, mut cache: ZorkCache, test_mode: bool) -> Result<()> { let cache_path = &program_data .build .output_dir @@ -117,7 +83,7 @@ pub fn save( .with_context(move || "Error saving data to the Zork++ cache") } -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Serialize, Deserialize, Default)] pub struct ZorkCache<'a> { pub compiler: CppCompiler, pub last_program_execution: DateTime, diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index da529533..26c298c1 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -107,7 +107,9 @@ impl CommandLineArguments for &Arguments {} impl core::fmt::Display for Arguments { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.iter().try_for_each(|arg| write!(f, "{}", arg)) + self.0.iter().try_for_each(|arg| write!(f, "{} ", arg)) + // TODO: there's an ugly space at the end of every command line when Display is invoked + // :) Just fix it } } diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index b85c9cbe..0c518c0c 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -33,6 +33,8 @@ pub fn run_generated_commands( log::info!("Proceeding to execute the generated commands..."); let general_args = &cache.generated_commands.general_args.get_args(); + let compiler_specific_shared_args = &cache.generated_commands.compiler_common_args.get_args(); + let env_args = cache.get_process_env_args().clone(); // TODO: this is yet better than clone the // generated commands (maybe) but I'm not // happy with it @@ -40,45 +42,41 @@ pub fn run_generated_commands( for sys_module in &cache.generated_commands.system_modules { // TODO: will be deprecated soon, hopefully // But while isn't deleted, we could normalize them into SourceCommandLine + // And then, consider to join them into the all generated commands iter execute_command(program_data, sys_module.1, &env_args)?; } - { - // Scopes for avoiding 'already borrow' errors on the different processes - // TODO: better move it to a custom procedure - let translation_units = cache - .generated_commands - .get_all_command_lines() - .filter(|scl| scl.need_to_build) - .collect::>(); - - for translation_unit_cmd in translation_units { - // Join the concrete args of any translation unit with the general flyweights - let translation_unit_cmd_args: Arguments = general_args - .iter() - .chain(translation_unit_cmd.args.iter()) - .collect(); - - let r = execute_command( - program_data, - &translation_unit_cmd_args, - &env_args, + let translation_units = cache + .generated_commands + .get_all_command_lines() + .filter(|scl| scl.need_to_build) + .collect::>(); + + for translation_unit_cmd in translation_units { + // Join the concrete args of any translation unit with the general flyweights + let translation_unit_cmd_args: Arguments = general_args + .iter() + .chain(compiler_specific_shared_args.iter()) + .chain(translation_unit_cmd.args.iter()) + .collect(); + + let r = execute_command(program_data, &translation_unit_cmd_args, &env_args); + translation_unit_cmd.execution_result = CommandExecutionResult::from(&r); + + if let Err(e) = r { + return Err(e); + } else if !r.as_ref().unwrap().success() { + let err = eyre!( + "Ending the program, because the build of: {:?} failed", + translation_unit_cmd.filename ); - translation_unit_cmd.execution_result = CommandExecutionResult::from(&r); - - if let Err(e) = r { - return Err(e); - } else if !r.as_ref().unwrap().success() { - let err = eyre!( - "Ending the program, because the build of: {:?} failed", - translation_unit_cmd.filename - ); - return Err(err); - } + return Err(err); } } if !cache.generated_commands.linker.args.is_empty() { + // TODO: consider to join this into the + // all args iterator log::debug!("Processing the linker command line..."); let r = execute_command( @@ -89,20 +87,15 @@ pub fn run_generated_commands( cache.generated_commands.linker.execution_result = CommandExecutionResult::from(&r); if let Err(e) = r { - // cache::save2(program_data, cache, test_mode)?; return Err(e); } else if !r.as_ref().unwrap().success() { - // cache::save2(program_data, cache, test_mode)?; return Err(eyre!( "Ending the program, because the linker command line execution failed", )); } } - /* let updated_commands = (*commands).borrow_mut().clone(); - cache.generated_commands = updated_commands; // TODO: we do really need another way of do this (we needed two clones) */ - // cache::save2(program_data, cache, test_mode)?; - Ok(CommandExecutionResult::Success) + Ok(CommandExecutionResult::Success) // TODO: consider a new variant, like AllSuccedeed } /// Executes a new [`std::process::Command`] to run the generated binary @@ -177,27 +170,6 @@ pub struct SourceCommandLine { } impl SourceCommandLine { - /// Chains the read-only iterators of the flyweights that holds the shared arguments, like [`CommonArgs`] and - /// the specific compiler ones [`CompilerCommonArguments`] to the specific ones of self.args, - /// in order to create a temporary view with all the command lines arguments that are required - /// to build a *C++ translation unit* - /* pub fn all_command_args<'a>( - &'a self, - general_args: &'a [Argument], - _compiler_specific_common_args: &'a [Argument], - ) -> impl Iterator + Debug + 'a { - general_args.iter().chain(self.args.iter()) - } */ - /* pub fn all_command_args<'a>( - &'a self, - commands: &'a Commands - ) - -> impl Iterator - // -> Vec<&'a Argument> - { - commands.general_args.get_args().iter().chain(self.args.iter()) - } */ - pub fn from_translation_unit( // TODO init it as a args holder, but doesn't have the status yet tu: impl TranslationUnit, @@ -241,8 +213,9 @@ impl SourceCommandLine { #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct LinkerCommandLine { // pub main: &'a Path, // TODO: can't this disappear? At the end of the day, is just another obj file - pub built_files: Vec, - pub args: Arguments, + pub built_files: Vec, // TODO: obj files? + pub args: Arguments, // TODO: :does the linker command line needs any different that the + // generals? pub execution_result: CommandExecutionResult, } @@ -261,7 +234,7 @@ impl LinkerCommandLine { } /// Holds the generated command line arguments for a concrete compiler -#[derive(Serialize, Clone, Deserialize, Default)] +#[derive(Serialize, Deserialize, Default)] pub struct Commands { pub compiler: CppCompiler, pub cpp_stdlib: Option, @@ -269,7 +242,8 @@ pub struct Commands { pub system_modules: HashMap, pub general_args: CommonArgs, - // pub compiler_common_args: Box, + pub compiler_common_args: Box, + pub interfaces: Vec, pub implementations: Vec, pub sources: Vec, @@ -277,30 +251,6 @@ pub struct Commands { } impl Commands { - pub fn new( - model: &ZorkModel<'_>, - general_args: CommonArgs, - _compiler_specific_common_args: Box, - ) -> Self { - Self { - // TODO: try to see if its possible to move around the code and have a From, avoiding default initialization, - // since this will always cause reallocations, and 'from' may be able to allocate at the exact required capacity - // of every collection - compiler: model.compiler.cpp_compiler, - - cpp_stdlib: None, - c_compat_stdlib: None, - - general_args, - // compiler_common_args: compiler_specific_common_args, - system_modules: HashMap::with_capacity(0), - interfaces: Vec::with_capacity(0), - implementations: Vec::with_capacity(0), - sources: Vec::with_capacity(0), - linker: LinkerCommandLine::default(), - } - } - /// Returns an [std::iter::Chain] (behind the opaque impl clause return type signature) /// which points to all the generated commmands for the two variants of the compilers vendors C++ modular /// standard libraries implementations (see: [crate::project_model::compiler::StdLibMode]) diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index 582217cf..3eec047d 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -21,7 +21,11 @@ impl CommonArgs { self.0.clone() } - pub fn get_args_slice(&self) -> impl Iterator> { + pub fn get_args_slice(&self) -> impl Iterator { + self.0.as_slice().iter() + } + + pub fn get_args_slice_rced(&self) -> impl Iterator> { self.0.as_slice().iter().map(Rc::new) } } @@ -29,6 +33,11 @@ impl CommonArgs { impl<'a> From<&'a ZorkModel<'_>> for CommonArgs { fn from(model: &'a ZorkModel<'_>) -> Self { let mut common_args = Arguments::default(); + // TODO: + // Aren't the common args in the end compiler specific ones? + // Should we remove this DS? + // Ah no. Maybe we can use it for hold things like -o (shared among all three (MSVC also + // accepts - in some args instead of /)) common_args.push(model.compiler.language_level_arg()); common_args.extend_from_slice(model.compiler.extra_args()); @@ -64,7 +73,9 @@ pub fn compiler_common_arguments_factory( /// Allows to have a common interface for any type that represents a data structure which its /// purpose is to hold common [`Argument`] across the diferent kind of [`TranslationUnit`] #[typetag::serde(tag = "type")] -pub trait CompilerCommonArguments {} +pub trait CompilerCommonArguments { + fn get_args(&self) -> Arguments; +} impl Default for Box { fn default() -> Self { Box::::default() // TODO: isn't this a code smell? @@ -77,11 +88,28 @@ impl Default for Box { /// TODO: the typetag library doesn't support yet the deserialization of generic impls, only /// serialization, so there's no point on having any primites #[typetag::serde] -impl CompilerCommonArguments for ClangCommonArgs {} +impl CompilerCommonArguments for ClangCommonArgs { + fn get_args(&self) -> Arguments { + let mut args = Arguments::default(); + args.push(self.std_lib.as_arg()); + args.create_and_push(&self.implicit_modules); + args.push(self.implicit_module_map.clone()); + args.create_and_push(&self.prebuilt_module_path); + args + } +} #[typetag::serde] -impl CompilerCommonArguments for MsvcCommonArgs {} +impl CompilerCommonArguments for MsvcCommonArgs { + fn get_args(&self) -> Arguments { + Arguments::default() + } +} #[typetag::serde] -impl CompilerCommonArguments for GccCommonArgs {} +impl CompilerCommonArguments for GccCommonArgs { + fn get_args(&self) -> Arguments { + Arguments::default() + } +} #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct ClangCommonArgs { diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 15d69a0c..af77b9f4 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -24,7 +24,7 @@ use crate::{ }, }; -use self::data_factory::{CommonArgs, CompilerCommonArguments}; +use self::data_factory::CommonArgs; /// The core procedure. Generates the commands that will be sent to the compiler /// for every translation unit declared by the user for its project @@ -33,20 +33,19 @@ pub fn generate_commands<'a>( cache: &mut ZorkCache, tests: bool, ) -> Result<()> { - // Generate the Flyweight struct, with the repetitive data and details of the command lines - let _general_args = CommonArgs::from(model); - let _compiler_specific_common_args: Box = - data_factory::compiler_common_arguments_factory(model); // TODO: guard it with a presence check - - cache.generated_commands.general_args = _general_args; - // cache. + // TODO: guard it with a presence check */ + // They should only be generated the first time or on every cache reset + cache.generated_commands.general_args = CommonArgs::from(model); + cache.generated_commands.compiler_common_args = + data_factory::compiler_common_arguments_factory(model); // TODO: add them to the commands DS, so they are together until they're generated // Build the std library as a module build_modular_stdlib(model, cache); // TODO: ward it with an if for only call this fn for the // 1st - Build the modules - if let Some(modules) = &model.modules { // TODO: re-think again this optional + if let Some(modules) = &model.modules { + // TODO: re-think again this optional // Pre-tasks if model.compiler.cpp_compiler == CppCompiler::GCC && !modules.sys_modules.is_empty() { helpers::build_sys_modules(model, cache) @@ -112,13 +111,12 @@ fn build_sources(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> R &model.executable.sourceset.sources }; - srcs.iter().for_each( - |src| { // TODO: yet unchanged, we need to replicate the idea on main - if !flag_source_file_without_changes(&model.compiler.cpp_compiler, cache, &src.file()) { - sources::generate_sources_arguments(model, cache, &model.tests, src); - } + srcs.iter().for_each(|src| { + // TODO: yet unchanged, we need to replicate the idea on main + if !flag_source_file_without_changes(&model.compiler.cpp_compiler, cache, &src.file()) { + sources::generate_sources_arguments(model, cache, &model.tests, src); } - ); + }); Ok(()) } @@ -404,21 +402,14 @@ mod sources { let out_dir: &Path = model.build.output_dir.as_ref(); let mut arguments = Arguments::default(); // TODO: provisional while we're implementing the Flyweights - arguments.push(model.compiler.language_level_arg()); - arguments.extend_from_slice(model.compiler.extra_args()); + /* arguments.push(model.compiler.language_level_arg()); + arguments.extend_from_slice(model.compiler.extra_args()); */ match compiler { CppCompiler::CLANG => { - // arguments.push_opt(model.compiler.stdlib_arg()); - // arguments.create_and_push("-fimplicit-modules"); arguments.create_and_push("-x"); arguments.create_and_push("c++-module"); arguments.create_and_push("--precompile"); - // arguments.push(clang_args::implicit_module_map(out_dir)); - /* arguments.create_and_push(format!( - "-fprebuilt-module-path={}/clang/modules/interfaces", - out_dir.display() - )); */ clang_args::add_direct_module_interfaces_dependencies( &interface.dependencies, compiler, diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 38c59cd8..18e8f71c 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -65,7 +65,7 @@ pub mod worker { let config_files: Vec = find_config_files(project_root, &cli_args.match_files) .with_context(|| "We didn't found a valid Zork++ configuration file")?; log::trace!("Config files found: {config_files:?}"); - // TODO add the last modified time + // TODO: add the last modified time for config_file in config_files { log::debug!( @@ -83,7 +83,7 @@ pub mod worker { let config = config_file::zork_cfg_from_file(raw_file.as_str()) .with_context(|| "Could not parse configuration file")?; let program_data = build_model(config, cli_args, &abs_project_root)?; - create_output_directory(&program_data)?; // TODO avoid this call without check if exists + create_output_directory(&program_data)?; // TODO: avoid this call without check if exists let cache = cache::load(&program_data, cli_args) .with_context(|| "Unable to load the Zork++ cache")?; @@ -109,17 +109,15 @@ pub mod worker { program_data: &'a ZorkModel<'_>, mut cache: ZorkCache, ) -> Result { - let is_tests_run = cli_args.command.eq(&Command::Test); generate_commands(program_data, &mut cache, false) .with_context(|| "Failed to generated the commands for the project")?; let execution_result = match cli_args.command { - Command::Build => - commands::run_generated_commands( program_data, &mut cache,), + Command::Build => commands::run_generated_commands(program_data, &mut cache), Command::Run | Command::Test => { - match commands::run_generated_commands( program_data, &mut cache,) { + match commands::run_generated_commands(program_data, &mut cache) { Ok(_) => commands::autorun_generated_binary( &program_data.compiler.cpp_compiler, &program_data.build.output_dir, @@ -127,7 +125,7 @@ pub mod worker { ), Err(e) => Err(e), } - }, + } _ => todo!( "This branch should never be reached for now, as do not exists commands that may\ trigger them. The unique remaining, is ::New, that is already processed\ diff --git a/zork++/src/lib/project_model/compiler.rs b/zork++/src/lib/project_model/compiler.rs index 7eb9f2dd..cdd9da05 100644 --- a/zork++/src/lib/project_model/compiler.rs +++ b/zork++/src/lib/project_model/compiler.rs @@ -162,6 +162,15 @@ pub enum StdLib { LIBCPP, } +impl StdLib { + pub fn as_arg(&self) -> Argument { + Argument::from(match *self { + StdLib::STDLIBCPP => "-stdlib=libstdc++", + StdLib::LIBCPP => "-stdlib=libc++", + }) + } +} + impl fmt::Display for StdLib { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_ref()) From 54f8b5632f70fd9db0439a0d00c5d19f3e71620a Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 1 Jul 2024 15:22:49 +0200 Subject: [PATCH 21/73] feat: Applying the latest changes to all the kind of translation units (raw sources) --- zork++/src/lib/cache/mod.rs | 12 ++++++------ zork++/src/lib/compiler/mod.rs | 36 +++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index f1d5c1bf..118d8d10 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -115,11 +115,11 @@ impl<'a> ZorkCache<'a> { .find(|mi| module_impl_model.file() == (*mi).path()) } - // pub fn get_source_cmd(&self, module_impl_model: &Source) -> Option<&SourceCommandLine>{ - // self.generated_commands.implementations.iter().find(|mi| - // module_impl_model.file() == (*mi).path() - // ) - // } + pub fn get_source_cmd(&mut self, module_impl_model: &T) -> Option<&mut SourceCommandLine>{ + self.generated_commands.sources.iter_mut().find(|mi| + module_impl_model.file() == (*mi).path() + ) + } /// Returns a [`Option`] of [`CommandDetails`] if the file is persisted already in the cache pub fn is_file_cached(&self, _path: impl AsRef) -> Option<&CommandDetail> { @@ -234,7 +234,7 @@ impl<'a> ZorkCache<'a> { None } - fn _normalize_execution_result_status( + fn normalize_execution_result_status( // TODO: pending to re-implement it // ALe, don't read again this. We just need to fix the implementation when the commands // are generated, or even better, bring them from the cache diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index af77b9f4..fde6527e 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -105,17 +105,29 @@ fn build_executable(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) - fn build_sources(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> Result<()> { log::info!("Generating the commands for the source files..."); - let srcs = if tests { - &model.tests.sourceset.sources + let (srcs, target_kind) = if tests { + (&model.tests.sourceset.sources, &model.tests as &dyn ExecutableTarget) } else { - &model.executable.sourceset.sources + (&model.executable.sourceset.sources, &model.executable as &dyn ExecutableTarget) }; - srcs.iter().for_each(|src| { - // TODO: yet unchanged, we need to replicate the idea on main - if !flag_source_file_without_changes(&model.compiler.cpp_compiler, cache, &src.file()) { - sources::generate_sources_arguments(model, cache, &model.tests, src); - } + let compiler = cache.compiler; + let lpe = cache.last_program_execution; + + srcs.iter().for_each(|source| { + if let Some(generated_cmd) = cache.get_source_cmd(source) { + let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, &lpe, generated_cmd, &source.file()); + log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &source.file()); + + if !translation_unit_must_be_rebuilt { + log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &source.file()); + } + generated_cmd.need_to_build = translation_unit_must_be_rebuilt; + } else { + sources::generate_sources_arguments(model, cache, target_kind, source) // TODO: wtf is + // the + // model.tests?? + }; }); Ok(()) @@ -152,9 +164,10 @@ fn process_module_interfaces<'a>( ) { // let c: Vec<&dyn TranslationUnit> = interfaces.iter().map(|mi| mi as &dyn TranslationUnit).collect(); // let d = c.iter().map(|tu| tu as &dyn ModuleInterfaceModel).collect(); // downcast, as isn't usable for non primitives + let compiler = cache.compiler; let lpe = cache.last_program_execution; + interfaces.iter().for_each(|module_interface| { - let compiler = cache.compiler; if let Some(generated_cmd) = cache.get_module_ifc_cmd(module_interface) { let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, &lpe, generated_cmd, &module_interface.file()); log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_interface.file()); @@ -175,9 +188,10 @@ fn process_module_implementations<'a>( cache: &mut ZorkCache, impls: &'a [ModuleImplementationModel], ) { + let compiler = cache.compiler; let lpe = cache.last_program_execution; // TODO: check for an Rc< or other solution, but closure below requires unique access + impls.iter().for_each(|module_impl| { - let compiler = cache.compiler; if let Some(generated_cmd) = cache.get_module_impl_cmd(module_impl) { let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, &lpe, generated_cmd, &module_impl.file()); log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_impl.file()); @@ -322,7 +336,7 @@ mod sources { pub fn generate_sources_arguments<'a>( model: &'a ZorkModel, cache: &mut ZorkCache, - target: &'a impl ExecutableTarget<'a>, + target: &'a (impl ExecutableTarget<'a> + ?Sized), source: &'a SourceFile, ) { let compiler = model.compiler.cpp_compiler; From 54215007e65b76b4677762ee9941f85087d03e61 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 2 Jul 2024 12:10:19 +0200 Subject: [PATCH 22/73] feat: The generated obj files that are added to the linker are appended at generation time only --- zork++/src/lib/cache/mod.rs | 12 +- zork++/src/lib/cli/output/arguments.rs | 15 ++- zork++/src/lib/cli/output/commands.rs | 21 ++-- zork++/src/lib/compiler/data_factory.rs | 74 ++++++++--- zork++/src/lib/compiler/mod.rs | 160 +++++------------------- 5 files changed, 118 insertions(+), 164 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 118d8d10..7037f734 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -115,10 +115,14 @@ impl<'a> ZorkCache<'a> { .find(|mi| module_impl_model.file() == (*mi).path()) } - pub fn get_source_cmd(&mut self, module_impl_model: &T) -> Option<&mut SourceCommandLine>{ - self.generated_commands.sources.iter_mut().find(|mi| - module_impl_model.file() == (*mi).path() - ) + pub fn get_source_cmd( + &mut self, + module_impl_model: &T, + ) -> Option<&mut SourceCommandLine> { + self.generated_commands + .sources + .iter_mut() + .find(|mi| module_impl_model.file() == (*mi).path()) } /// Returns a [`Option`] of [`CommandDetails`] if the file is persisted already in the cache diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index 26c298c1..e6999bfd 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -186,11 +186,6 @@ impl IntoIterator for &Arguments { self.0.clone().into_iter() } } -/* impl FromIterator> for Arguments { - fn from_iter>(iter: T) -> Self { - todo!() - } -} */ impl FromIterator for Arguments { fn from_iter>(iter: I) -> Self { @@ -202,6 +197,16 @@ impl FromIterator for Arguments { } } +impl FromIterator for &Arguments { + fn from_iter>(iter: I) -> Self { + let mut vec = Vec::new(); + for item in iter { + vec.push(item); + } + &Arguments(vec) + } +} + impl<'a> FromIterator<&'a Argument> for Arguments { fn from_iter>(iter: I) -> Arguments { let mut vec = Vec::new(); diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index 0c518c0c..69aa978a 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -53,7 +53,7 @@ pub fn run_generated_commands( .collect::>(); for translation_unit_cmd in translation_units { - // Join the concrete args of any translation unit with the general flyweights + // Join the concrete args of any translation unit with the ones held in the flyweights let translation_unit_cmd_args: Arguments = general_args .iter() .chain(compiler_specific_shared_args.iter()) @@ -75,13 +75,17 @@ pub fn run_generated_commands( } if !cache.generated_commands.linker.args.is_empty() { - // TODO: consider to join this into the - // all args iterator log::debug!("Processing the linker command line..."); + let linker_cmdline_args = general_args + .iter() + .chain(compiler_specific_shared_args.iter()) + .chain(cache.generated_commands.linker.args.iter()) + .collect::(); // TODO: can we avoid clone and own all the args? just use the + // .iter() as a view? let r = execute_command( program_data, - &cache.generated_commands.linker.args, + &linker_cmdline_args, &env_args, ); cache.generated_commands.linker.execution_result = CommandExecutionResult::from(&r); @@ -96,6 +100,7 @@ pub fn run_generated_commands( } Ok(CommandExecutionResult::Success) // TODO: consider a new variant, like AllSuccedeed + // or better, change the return for something better } /// Executes a new [`std::process::Command`] to run the generated binary @@ -132,7 +137,6 @@ pub fn autorun_generated_binary( fn execute_command( model: &ZorkModel, arguments: T, - // cache: &ZorkCache, env_vars: &EnvVars, ) -> Result where @@ -155,7 +159,6 @@ where .with_context(|| format!("[{compiler}] - Command {arguments} failed!")) } -/// The pieces and details for the generated command line /// for some translation unit /// /// * args* : member that holds all the cmd arguments that will be passed to the compiler driver @@ -224,12 +227,14 @@ impl LinkerCommandLine { /// in order to add it to the files that will be linked to generate the final product /// in the two-phase compilation model pub fn add_buildable_at(&mut self, path: &Path) { - self.built_files.push(path.to_path_buf()); + self.args.push(Argument::from(path)); } + // TODO: just maybe a Cow for everyone? + /// Owned version of TODO link pub fn add_owned_buildable_at(&mut self, path: PathBuf) { - self.built_files.push(path); + self.args.push(path.into()); } } diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index 3eec047d..06dec60f 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::{ bounds::ExtraArgs, + cache::ZorkCache, cli::output::arguments::{clang_args, Argument, Arguments}, project_model::compiler::{CppCompiler, StdLib}, project_model::ZorkModel, @@ -58,11 +59,13 @@ impl IntoIterator for CommonArgs { /// command line for every translation unit, regardeless the underlying choosen compiler pub fn compiler_common_arguments_factory( model: &ZorkModel<'_>, + cache: &ZorkCache<'_>, ) -> Box { - // TODO: consider having a union (enum) instead of a fat ptr + // TODO: consider having a union (enum) instead of a fat ptr, so we can serialize the data + // and introduce a lifetime on the Argument type to use Cow instead of String match model.compiler.cpp_compiler { CppCompiler::CLANG => Box::new(ClangCommonArgs::new(model)), - CppCompiler::MSVC => Box::new(MsvcCommonArgs::new()), + CppCompiler::MSVC => Box::new(MsvcCommonArgs::new(model, cache)), CppCompiler::GCC => Box::new(GccCommonArgs::new()), } } @@ -101,13 +104,32 @@ impl CompilerCommonArguments for ClangCommonArgs { #[typetag::serde] impl CompilerCommonArguments for MsvcCommonArgs { fn get_args(&self) -> Arguments { - Arguments::default() + let mut args = Arguments::default(); + args.create_and_push(&self.exception_handling_model); + args.create_and_push(&self.no_logo); + args.create_and_push(&self.no_compile); + + args.create_and_push(&self.ifc_search_dir); + args.create_and_push(&*self.ifc_search_dir_value); + + args.create_and_push("/reference"); + args.create_and_push(format! { + "std={}", self.stdlib_ref_path.display() + }); + args.create_and_push("/reference"); + args.create_and_push(format! { + "std.compat={}", self.c_compat_stdlib_ref_path.display() + }); + args } } #[typetag::serde] impl CompilerCommonArguments for GccCommonArgs { fn get_args(&self) -> Arguments { - Arguments::default() + let mut args = Arguments::default(); + args.create_and_push("-fmodules-ts"); + args.create_and_push("-c"); + args } } @@ -140,27 +162,49 @@ impl ClangCommonArgs { #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct MsvcCommonArgs { exception_handling_model: Cow<'static, str>, - /* no_logo: &'a str, - no_compile: &'a str, // TODO: should be in the general and pass in the model? */ - // ref_stdlib: &'static str, // TODO: this are tecnically two args, /reference and the value - // ref_stdlib_compat: &'static str, // TODO: this are tecnically two args, /reference and the value - // TODO: split the dual cases per switches - // TODO: can we have switches like tuples? like switch-value pairs? + no_logo: Cow<'static, str>, + no_compile: Cow<'static, str>, + reference: Cow<'static, str>, + ifc_search_dir: Cow<'static, str>, + ifc_search_dir_value: Cow<'static, Path>, + stdlib_ref_path: Cow<'static, Path>, + c_compat_stdlib_ref_path: Cow<'static, Path>, } impl MsvcCommonArgs { - pub fn new() -> Self { + pub fn new(model: &ZorkModel<'_>, cache: &ZorkCache<'_>) -> Self { + let out_dir: &Path = model.build.output_dir.as_ref(); + Self { exception_handling_model: Cow::Borrowed("/EHsc"), - /* no_logo: "nologo", - no_compile: "/c", */ + no_logo: Cow::Borrowed("/nologo"), + no_compile: Cow::Borrowed("/c"), + reference: Cow::Borrowed("/reference"), + + ifc_search_dir: Cow::Borrowed("/ifcSearchDir"), + ifc_search_dir_value: Cow::Owned( + out_dir + .join(model.compiler.cpp_compiler.as_ref()) + .join("modules") + .join("interfaces"), + ), + stdlib_ref_path: Cow::Owned(cache.compilers_metadata.msvc.stdlib_bmi_path.clone()), + c_compat_stdlib_ref_path: Cow::Owned( + cache.compilers_metadata.msvc.c_stdlib_bmi_path.clone(), + ), } } } #[derive(Serialize, Deserialize, Default, Debug, Clone)] -pub struct GccCommonArgs {} +pub struct GccCommonArgs { + compile_but_dont_link: Cow<'static, str>, + modules_ts: Cow<'static, str>, +} impl GccCommonArgs { pub fn new() -> Self { - Self {} + Self { + compile_but_dont_link: Cow::Borrowed("-c"), + modules_ts: Cow::Borrowed("-fmodules-ts"), + } } } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index fde6527e..a60a3b0d 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -11,7 +11,6 @@ use std::path::Path; use crate::bounds::{ExecutableTarget, ExtraArgs, TranslationUnit}; use crate::cli::output::arguments::{clang_args, msvc_args, Arguments}; use crate::cli::output::commands::SourceCommandLine; -use crate::compiler::helpers::flag_source_file_without_changes; use crate::project_model::compiler::StdLibMode; use crate::utils::constants; use crate::{ @@ -37,11 +36,11 @@ pub fn generate_commands<'a>( // They should only be generated the first time or on every cache reset cache.generated_commands.general_args = CommonArgs::from(model); cache.generated_commands.compiler_common_args = - data_factory::compiler_common_arguments_factory(model); + data_factory::compiler_common_arguments_factory(model, cache); // TODO: add them to the commands DS, so they are together until they're generated // Build the std library as a module - build_modular_stdlib(model, cache); // TODO: ward it with an if for only call this fn for the + generate_modular_stdlibs_cmds(model, cache); // TODO: ward it with an if for only call this fn for the // 1st - Build the modules if let Some(modules) = &model.modules { @@ -61,9 +60,9 @@ pub fn generate_commands<'a>( Ok(()) } -/// Builds the C++ standard library as a pre-step acording to the specification +/// Generates the cmds for build the C++ standard libraries (std and std.compat) acording to the specification /// of each compiler vendor -fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache) { +fn generate_modular_stdlibs_cmds(model: &ZorkModel<'_>, cache: &mut ZorkCache) { let compiler = model.compiler.cpp_compiler; // TODO: remaining ones: Clang, GCC @@ -72,17 +71,14 @@ fn build_modular_stdlib(model: &ZorkModel<'_>, cache: &mut ZorkCache) { let built_stdlib_path = &cache.compilers_metadata.msvc.stdlib_bmi_path; if !built_stdlib_path.exists() { - log::info!( - "Building the {:?} C++ standard library implementation", - compiler - ); + log::info!("Generating the command for build the {:?} C++ standard library implementation", compiler); let cpp_stdlib = msvc_args::generate_std_cmd(model, cache, StdLibMode::Cpp); cache.generated_commands.cpp_stdlib = Some(cpp_stdlib); } let built_stdlib_compat_path = &cache.compilers_metadata.msvc.c_stdlib_bmi_path; if !built_stdlib_compat_path.exists() { - log::info!("Building the {:?} C compat CPP std lib", compiler); + log::info!("Generating the command for build the {:?} C compat C++ standard library", compiler); let c_compat = msvc_args::generate_std_cmd(model, cache, StdLibMode::CCompat); cache.generated_commands.c_compat_stdlib = Some(c_compat); } @@ -106,9 +102,15 @@ fn build_executable(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) - fn build_sources(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> Result<()> { log::info!("Generating the commands for the source files..."); let (srcs, target_kind) = if tests { - (&model.tests.sourceset.sources, &model.tests as &dyn ExecutableTarget) + ( + &model.tests.sourceset.sources, + &model.tests as &dyn ExecutableTarget, + ) } else { - (&model.executable.sourceset.sources, &model.executable as &dyn ExecutableTarget) + ( + &model.executable.sourceset.sources, + &model.executable as &dyn ExecutableTarget, + ) }; let compiler = cache.compiler; @@ -116,7 +118,7 @@ fn build_sources(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> R srcs.iter().for_each(|source| { if let Some(generated_cmd) = cache.get_source_cmd(source) { - let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, &lpe, generated_cmd, &source.file()); + let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_built(compiler, &lpe, generated_cmd, &source.file()); log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &source.file()); if !translation_unit_must_be_rebuilt { @@ -169,11 +171,13 @@ fn process_module_interfaces<'a>( interfaces.iter().for_each(|module_interface| { if let Some(generated_cmd) = cache.get_module_ifc_cmd(module_interface) { - let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, &lpe, generated_cmd, &module_interface.file()); + let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_built(compiler, &lpe, generated_cmd, &module_interface.file()); log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_interface.file()); if !translation_unit_must_be_rebuilt { log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &module_interface.file()); + // TODO: here is where we should use the normalize_execution_result_status + // function, to mark executions as cached (when tu musn't be rebuilt) } generated_cmd.need_to_build = translation_unit_must_be_rebuilt; } else { @@ -193,7 +197,7 @@ fn process_module_implementations<'a>( impls.iter().for_each(|module_impl| { if let Some(generated_cmd) = cache.get_module_impl_cmd(module_impl) { - let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_rebuilt(compiler, &lpe, generated_cmd, &module_impl.file()); + let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_built(compiler, &lpe, generated_cmd, &module_impl.file()); log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_impl.file()); if !translation_unit_must_be_rebuilt { @@ -219,8 +223,8 @@ pub fn generate_main_command_line_args<'a>( let executable_name = target.name(); let mut arguments = Arguments::default(); - arguments.push(model.compiler.language_level_arg()); - arguments.extend_from_slice(model.compiler.extra_args()); + /* arguments.push(model.compiler.language_level_arg()); + arguments.extend_from_slice(model.compiler.extra_args()); */ arguments.extend_from_slice(target.extra_args()); match compiler { @@ -288,24 +292,7 @@ pub fn generate_main_command_line_args<'a>( } }; - arguments.extend( - cache - .generated_commands - .linker - .built_files - .iter() - .map(Argument::from), - ); // TODO can't we avoid this, and just add the pathbufs? - cache.generated_commands.linker.args.extend(arguments); - cache.generated_commands.linker.built_files = target // TODO: built_files means raw cpp sources - // TODO: add a custom collector on the mod sources - // TODO: just name the field 'sources' - .sourceset() - .sources - .iter() - .map(|s| s.file()) - .collect::>(); Ok(()) } @@ -415,9 +402,7 @@ mod sources { let compiler = model.compiler.cpp_compiler; let out_dir: &Path = model.build.output_dir.as_ref(); - let mut arguments = Arguments::default(); // TODO: provisional while we're implementing the Flyweights - /* arguments.push(model.compiler.language_level_arg()); - arguments.extend_from_slice(model.compiler.extra_args()); */ + let mut arguments = Arguments::default(); match compiler { CppCompiler::CLANG => { @@ -443,26 +428,10 @@ mod sources { arguments.create_and_push(interface.file()); } CppCompiler::MSVC => { - arguments.create_and_push("/EHsc"); - arguments.create_and_push("/nologo"); - arguments.create_and_push("/c"); - - arguments.create_and_push("/reference"); - arguments.create_and_push(format! { - "std={}", cache.compilers_metadata.msvc.stdlib_bmi_path.display() - }); - arguments.create_and_push("/reference"); - arguments.create_and_push(format! { - "std.compat={}", cache.compilers_metadata.msvc.c_stdlib_bmi_path.display() - }); let implicit_lookup_mius_path = out_dir .join(compiler.as_ref()) .join("modules") - .join("interfaces") - .display() - .to_string(); // TODO Can we avoid this conversions? - arguments.create_and_push("/ifcSearchDir"); - arguments.create_and_push(implicit_lookup_mius_path.clone()); + .join("interfaces"); arguments.create_and_push("/ifcOutput"); arguments.create_and_push(implicit_lookup_mius_path); @@ -488,9 +457,9 @@ mod sources { } CppCompiler::GCC => { arguments.create_and_push("-fmodules-ts"); + arguments.create_and_push("-c"); arguments.create_and_push("-x"); arguments.create_and_push("c++"); - arguments.create_and_push("-c"); // The input file arguments.create_and_push(interface.file()); // The output file @@ -517,7 +486,6 @@ mod sources { let out_dir = model.build.output_dir.as_ref(); let mut arguments = Arguments::default(); - arguments.push(model.compiler.language_level_arg()); arguments.extend_from_slice(model.compiler.extra_args()); match compiler { @@ -551,24 +519,6 @@ mod sources { arguments.create_and_push(implementation.file()) } CppCompiler::MSVC => { - arguments.create_and_push("/EHsc"); - arguments.create_and_push("/nologo"); - arguments.create_and_push("/c"); - arguments.create_and_push("/reference"); - arguments.create_and_push(format! { - "std={}", cache.compilers_metadata.msvc.stdlib_bmi_path.display() - }); - arguments.create_and_push("/reference"); - arguments.create_and_push(format! { - "std.compat={}", cache.compilers_metadata.msvc.c_stdlib_bmi_path.display() - }); - arguments.create_and_push("/ifcSearchDir"); - arguments.create_and_push( - out_dir - .join(compiler.as_ref()) - .join("modules") - .join("interfaces"), - ); // The input file arguments.create_and_push(implementation.file()); // The output .obj file @@ -585,8 +535,6 @@ mod sources { arguments.create_and_push(format!("/Fo{}", obj_file_path.display())); } CppCompiler::GCC => { - arguments.create_and_push("-fmodules-ts"); - arguments.create_and_push("-c"); // The input file arguments.create_and_push(implementation.file()); // The output file @@ -757,62 +705,10 @@ mod helpers { } } - /// Marks the given source file as already processed, - /// or if it should be reprocessed again due to a previous failure status, - /// to avoid losing time rebuilding it if the translation unit - /// hasn't been modified since the last build process iteration. - /// - /// True means 'already processed with previous iteration: Success' and stored on the cache - pub(crate) fn flag_source_file_without_changes( - // TODO: kind of `look_for_tu_on_cache_or_generate_command - compiler: &CppCompiler, - cache: &ZorkCache, - file: &Path, - ) -> bool { - // TODO: it should return an Optional with the SourceCommandLine from the cache if its already there - if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { - // TODO: Review this - // with the new Clang - // versions - log::trace!("Module unit {file:?} will be rebuilt since we've detected that you are using Clang in Windows"); - return false; - } - // Check first if the file is already on the cache, and if it's last iteration was successful - if let Some(cached_compiled_file) = cache.is_file_cached(file) { - let execution_result = cached_compiled_file.execution_result(); - if execution_result != CommandExecutionResult::Success - && execution_result != CommandExecutionResult::Cached - { - log::trace!( - "File {file:?} with status: {:?}. Marked to reprocess", - execution_result - ); - return false; - }; - - // If exists and was successful, let's see if has been modified after the program last iteration - let last_process_timestamp = cache.last_program_execution; - let file_metadata = file.metadata(); - match file_metadata { - Ok(m) => match m.modified() { - Ok(modified) => DateTime::::from(modified) < last_process_timestamp, - Err(e) => { - log::error!("An error happened trying to get the last time that the {file:?} was modified. Processing it anyway because {e:?}"); - false - } - }, - Err(e) => { - log::error!("An error happened trying to retrieve the metadata of {file:?}. Processing it anyway because {e:?}"); - false - } - } - } else { - false - } - } - - /// TODO - pub(crate) fn translation_unit_must_be_rebuilt( + /// Checks if some user declared [TranslationUnit] must be built (for example, on the first + /// iteration it will always be the case), or if the file didn't had changes since the last + /// Zork++ run and therefore, we can avoid rebuilt it + pub(crate) fn translation_unit_must_be_built( // TODO: separation of concerns? Please // Just make two fns, the one that checks for the status and the one that checks for modifications // then just use a template-factory design pattern by just abstracting away the two checks in one call From cacfa5194bcbc0aace6413cb34cfb290a47a829a Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 3 Jul 2024 16:55:17 +0200 Subject: [PATCH 23/73] fix: removing the duplicated std byproducts (obj files) on the linker command line --- zork++/src/lib/cache/mod.rs | 23 ++--- zork++/src/lib/cli/output/arguments.rs | 22 +---- zork++/src/lib/cli/output/commands.rs | 42 ++------- zork++/src/lib/compiler/mod.rs | 118 ++++++++++++++++--------- 4 files changed, 91 insertions(+), 114 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 7037f734..bd10c6cc 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -89,12 +89,15 @@ pub struct ZorkCache<'a> { pub last_program_execution: DateTime, pub compilers_metadata: CompilersMetadata<'a>, pub generated_commands: Commands, + // pub new_commands: bool //< if the current iteration added some new command with respect the + // previous one } impl<'a> ZorkCache<'a> { pub fn last_program_execution(&self) -> &DateTime { &self.last_program_execution } + pub fn get_module_ifc_cmd( &mut self, module_interface_model: &ModuleInterfaceModel, @@ -125,22 +128,6 @@ impl<'a> ZorkCache<'a> { .find(|mi| module_impl_model.file() == (*mi).path()) } - /// Returns a [`Option`] of [`CommandDetails`] if the file is persisted already in the cache - pub fn is_file_cached(&self, _path: impl AsRef) -> Option<&CommandDetail> { - // let last_iteration_details = self.generated_commands.last(); - - // TODO: what a cost. We need to join them for every iteration and every file - // if let Some(last_iteration) = last_iteration_details { - // return last_iteration - // .interfaces - // .iter() - // .chain(last_iteration.implementations.iter()) - // .chain(last_iteration.sources.iter()) - // .find(|comm_det| comm_det.file_path().eq(path.as_ref())); - // } - None - } - /// The tasks associated with the cache after load it from the file system pub fn run_tasks(&mut self, program_data: &'a ZorkModel<'_>) -> Result<()> { let compiler = program_data.compiler.cpp_compiler; @@ -238,7 +225,7 @@ impl<'a> ZorkCache<'a> { None } - fn normalize_execution_result_status( + /* fn normalize_execution_result_status( // TODO: pending to re-implement it // ALe, don't read again this. We just need to fix the implementation when the commands // are generated, or even better, bring them from the cache @@ -262,7 +249,7 @@ impl<'a> ZorkCache<'a> { } else { module_command_line.execution_result } - } + } */ /// Method that returns the HashMap that holds the environmental variables that must be passed /// to the underlying shell diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index e6999bfd..eafa5c29 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -197,16 +197,6 @@ impl FromIterator for Arguments { } } -impl FromIterator for &Arguments { - fn from_iter>(iter: I) -> Self { - let mut vec = Vec::new(); - for item in iter { - vec.push(item); - } - &Arguments(vec) - } -} - impl<'a> FromIterator<&'a Argument> for Arguments { fn from_iter>(iter: I) -> Arguments { let mut vec = Vec::new(); @@ -283,7 +273,7 @@ pub mod msvc_args { use crate::{ bounds::TranslationUnit, cache::ZorkCache, - cli::output::commands::{CommandExecutionResult, SourceCommandLine}, + cli::output::commands::SourceCommandLine, project_model::{compiler::StdLibMode, ZorkModel}, }; @@ -311,9 +301,6 @@ pub mod msvc_args { ) }; - arguments.push(model.compiler.language_level_arg()); - arguments.create_and_push("/EHsc"); - arguments.create_and_push("/nologo"); arguments.create_and_push("/W4"); arguments.create_and_push("/reference"); @@ -331,11 +318,6 @@ pub mod msvc_args { "/Fo{}", stdlib_obj_path.display() }); - SourceCommandLine::from_translation_unit( - stdlib_sf, - arguments, - false, - CommandExecutionResult::default(), - ) + SourceCommandLine::new(stdlib_sf, arguments) } } diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index 69aa978a..693b8ba7 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -76,18 +76,13 @@ pub fn run_generated_commands( if !cache.generated_commands.linker.args.is_empty() { log::debug!("Processing the linker command line..."); - let linker_cmdline_args = general_args - .iter() - .chain(compiler_specific_shared_args.iter()) - .chain(cache.generated_commands.linker.args.iter()) - .collect::(); // TODO: can we avoid clone and own all the args? just use the - // .iter() as a view? let r = execute_command( program_data, - &linker_cmdline_args, + &cache.generated_commands.linker.args, &env_args, ); + cache.generated_commands.linker.execution_result = CommandExecutionResult::from(&r); if let Err(e) = r { @@ -173,28 +168,7 @@ pub struct SourceCommandLine { } impl SourceCommandLine { - pub fn from_translation_unit( - // TODO init it as a args holder, but doesn't have the status yet - tu: impl TranslationUnit, - args: Arguments, // TODO: maybe this should be an option? Cached arguments are passed - // here as default. So probably, even better than having an optional, - // we must replicate this to have a separate entity like - // CachedSourceCommandLine, and them just call them over any kind of - // constrained over some bound that wraps the operation of - // distinguish between them or not - processed: bool, - execution_result: CommandExecutionResult, - ) -> Self { - Self { - directory: tu.path(), - filename: tu.file_with_extension(), - args, - need_to_build: !processed, - execution_result, - } - } - - pub fn for_translation_unit( + pub fn new( // TODO init it as a args holder, but doesn't have the status yet tu: impl TranslationUnit, args: Arguments, @@ -215,10 +189,7 @@ impl SourceCommandLine { #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct LinkerCommandLine { - // pub main: &'a Path, // TODO: can't this disappear? At the end of the day, is just another obj file - pub built_files: Vec, // TODO: obj files? - pub args: Arguments, // TODO: :does the linker command line needs any different that the - // generals? + pub args: Arguments, pub execution_result: CommandExecutionResult, } @@ -241,7 +212,8 @@ impl LinkerCommandLine { /// Holds the generated command line arguments for a concrete compiler #[derive(Serialize, Deserialize, Default)] pub struct Commands { - pub compiler: CppCompiler, + pub compiler: CppCompiler, // TODO: review if we can afford this field given the new + // architechture pub cpp_stdlib: Option, pub c_compat_stdlib: Option, pub system_modules: HashMap, @@ -318,6 +290,8 @@ pub enum CommandExecutionResult { Cached, /// A command which is return code indicates an unsuccessful execution Failed, + /// Whenever a translation unit must be rebuilt + PendingToBuild, /// The execution failed, returning a [`Result`] with the Err variant Error, /// A previous state before executing a command line diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index a60a3b0d..1b0b6cc9 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -8,9 +8,9 @@ pub mod data_factory; use color_eyre::Result; use std::path::Path; -use crate::bounds::{ExecutableTarget, ExtraArgs, TranslationUnit}; +use crate::bounds::{ExecutableTarget, TranslationUnit}; use crate::cli::output::arguments::{clang_args, msvc_args, Arguments}; -use crate::cli::output::commands::SourceCommandLine; +use crate::cli::output::commands::{CommandExecutionResult, SourceCommandLine}; use crate::project_model::compiler::StdLibMode; use crate::utils::constants; use crate::{ @@ -53,9 +53,9 @@ pub fn generate_commands<'a>( process_modules(model, cache)?; }; // 2nd - Generate the commands for the non-module sources - build_sources(model, cache, tests)?; - // 3rd - Build the executable or the tests // TODO: commentary and fn name - build_executable(model, cache, tests)?; + generate_sources_cmds_args(model, cache, tests)?; + // 3rd - Genates the commands for the 'targets' declared by the user + generate_linkage_targets_commands(model, cache, tests)?; Ok(()) } @@ -64,42 +64,92 @@ pub fn generate_commands<'a>( /// of each compiler vendor fn generate_modular_stdlibs_cmds(model: &ZorkModel<'_>, cache: &mut ZorkCache) { let compiler = model.compiler.cpp_compiler; + let lpe = cache.last_program_execution; // TODO: remaining ones: Clang, GCC - // TODO: try to abstract the procedures into just one entity if compiler.eq(&CppCompiler::MSVC) { - let built_stdlib_path = &cache.compilers_metadata.msvc.stdlib_bmi_path; + let vs_stdlib_path = &cache + .compilers_metadata + .msvc + .vs_stdlib_path + .as_ref() + .unwrap() + .path(); + if let Some(cpp_stdlib_cmd) = cache.generated_commands.cpp_stdlib.as_mut() { + let build_std = helpers::translation_unit_must_be_built( + compiler, + &lpe, + cpp_stdlib_cmd, + vs_stdlib_path, + ); - if !built_stdlib_path.exists() { - log::info!("Generating the command for build the {:?} C++ standard library implementation", compiler); + cpp_stdlib_cmd.need_to_build = build_std; + if !build_std && !cpp_stdlib_cmd.execution_result.eq(&CommandExecutionResult::Cached) { + cpp_stdlib_cmd.execution_result = CommandExecutionResult::Cached; + } else { + cpp_stdlib_cmd.execution_result = CommandExecutionResult::PendingToBuild; + } + } else { + log::info!( + "Generating the command for build the {:?} C++ standard library implementation", + compiler + ); let cpp_stdlib = msvc_args::generate_std_cmd(model, cache, StdLibMode::Cpp); cache.generated_commands.cpp_stdlib = Some(cpp_stdlib); } - let built_stdlib_compat_path = &cache.compilers_metadata.msvc.c_stdlib_bmi_path; - if !built_stdlib_compat_path.exists() { - log::info!("Generating the command for build the {:?} C compat C++ standard library", compiler); - let c_compat = msvc_args::generate_std_cmd(model, cache, StdLibMode::CCompat); - cache.generated_commands.c_compat_stdlib = Some(c_compat); + let vs_ccompat_stdlib_path = &cache + .compilers_metadata + .msvc + .vs_c_stdlib_path + .as_ref() + .unwrap() + .path(); + if let Some(ccompat_stdlib_cmd) = cache.generated_commands.c_compat_stdlib.as_mut() { + let build_ccompat = helpers::translation_unit_must_be_built( + compiler, + &lpe, + ccompat_stdlib_cmd, + vs_ccompat_stdlib_path, + ); + + ccompat_stdlib_cmd.need_to_build = build_ccompat; + if !build_ccompat && !ccompat_stdlib_cmd.execution_result.eq(&CommandExecutionResult::Cached) { + ccompat_stdlib_cmd.execution_result = CommandExecutionResult::Cached; + } else { + ccompat_stdlib_cmd.execution_result = CommandExecutionResult::PendingToBuild; + } + } else { + log::info!( + "Generating the command for build the {:?} C++ standard library implementation", + compiler + ); + let ccompat_stdlib = msvc_args::generate_std_cmd(model, cache, StdLibMode::CCompat); + cache.generated_commands.c_compat_stdlib = Some(ccompat_stdlib); } } } -/// Triggers the build process for compile the source files declared for the project +/// Generates the command line that will be passed to the linker to generate an [`ExecutableTarget`] +/// +/// Legacy: /// If this flow is enabled by the Cli arg `Tests`, then the executable will be generated /// for the files and properties declared for the tests section in the configuration file -fn build_executable(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> Result<()> { +fn generate_linkage_targets_commands(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> Result<()> { // TODO: Check if the command line is the same as the previous? If there's no new sources? // And avoid re-executing? - // TODO refactor this code, just having the if-else branch inside the fn + // TODO: refactor this code, just having the if-else branch inside the fn + // Also, shouldn't we start to think about named targets? So introduce the static and dynamic + // libraries wouldn't be such a pain? if tests { - generate_main_command_line_args(model, cache, &model.tests) + generate_linker_command_line_args(model, cache, &model.tests) // TODO: shouldn't tests be + // just a target? } else { - generate_main_command_line_args(model, cache, &model.executable) + generate_linker_command_line_args(model, cache, &model.executable) } } -fn build_sources(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> Result<()> { +fn generate_sources_cmds_args(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> Result<()> { log::info!("Generating the commands for the source files..."); let (srcs, target_kind) = if tests { ( @@ -119,7 +169,6 @@ fn build_sources(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> R srcs.iter().for_each(|source| { if let Some(generated_cmd) = cache.get_source_cmd(source) { let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_built(compiler, &lpe, generated_cmd, &source.file()); - log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &source.file()); if !translation_unit_must_be_rebuilt { log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &source.file()); @@ -172,7 +221,6 @@ fn process_module_interfaces<'a>( interfaces.iter().for_each(|module_interface| { if let Some(generated_cmd) = cache.get_module_ifc_cmd(module_interface) { let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_built(compiler, &lpe, generated_cmd, &module_interface.file()); - log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_interface.file()); if !translation_unit_must_be_rebuilt { log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &module_interface.file()); @@ -198,7 +246,6 @@ fn process_module_implementations<'a>( impls.iter().for_each(|module_impl| { if let Some(generated_cmd) = cache.get_module_impl_cmd(module_impl) { let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_built(compiler, &lpe, generated_cmd, &module_impl.file()); - log::trace!("Source file: {:?} must be rebuilt: {translation_unit_must_be_rebuilt}", &module_impl.file()); if !translation_unit_must_be_rebuilt { log::trace!("Source file: {:?} was not modified since the last iteration. No need to rebuilt it again.", &module_impl.file()); @@ -211,20 +258,18 @@ fn process_module_implementations<'a>( } /// Generates the command line arguments for the desired target -pub fn generate_main_command_line_args<'a>( +pub fn generate_linker_command_line_args<'a>( model: &'a ZorkModel, cache: &mut ZorkCache, target: &'a impl ExecutableTarget<'a>, ) -> Result<()> { - log::info!("Generating the main command line..."); + log::info!("Generating the linker command line..."); let compiler = &model.compiler.cpp_compiler; let out_dir: &Path = model.build.output_dir.as_ref(); let executable_name = target.name(); let mut arguments = Arguments::default(); - /* arguments.push(model.compiler.language_level_arg()); - arguments.extend_from_slice(model.compiler.extra_args()); */ arguments.extend_from_slice(target.extra_args()); match compiler { @@ -274,9 +319,6 @@ pub fn generate_main_command_line_args<'a>( .with_extension(constants::BINARY_EXTENSION) .display() )); - // Add the .obj file of the modular stdlib to the linker command - arguments.create_and_push(&cache.compilers_metadata.msvc.stdlib_obj_path); - arguments.create_and_push(&cache.compilers_metadata.msvc.c_stdlib_obj_path); } CppCompiler::GCC => { arguments.create_and_push("-fmodules-ts"); @@ -308,10 +350,7 @@ mod sources { use crate::project_model::sourceset::SourceFile; use crate::{ bounds::{ExecutableTarget, TranslationUnit}, - cli::output::{ - arguments::clang_args, - commands::{CommandExecutionResult, SourceCommandLine}, - }, + cli::output::{arguments::clang_args, commands::SourceCommandLine}, project_model::{ compiler::CppCompiler, modules::{ModuleImplementationModel, ModuleInterfaceModel}, @@ -381,12 +420,7 @@ mod sources { arguments.create_and_push(format!("{fo}{}", obj_file.display())); arguments.create_and_push(source.file()); - let command_line = SourceCommandLine::from_translation_unit( - source, - arguments, - false, - CommandExecutionResult::default(), - ); + let command_line = SourceCommandLine::new(source, arguments); cache.generated_commands.sources.push(command_line); cache .generated_commands @@ -472,7 +506,7 @@ mod sources { } } - let cmd_line = SourceCommandLine::for_translation_unit(interface, arguments); + let cmd_line = SourceCommandLine::new(interface, arguments); cache.generated_commands.interfaces.push(cmd_line); } @@ -548,7 +582,7 @@ mod sources { } } - let cmd = SourceCommandLine::for_translation_unit(implementation, arguments); + let cmd = SourceCommandLine::new(implementation, arguments); cache.generated_commands.implementations.push(cmd); } } From ec8bdd32a485118044c1c3746b1dca74c2b0058e Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 4 Jul 2024 18:24:34 +0200 Subject: [PATCH 24/73] feat: Introducing the transient crate to allow downcasting the implementors of TranslationUnit when they're behind a impl TranslationUnit --- zork++/Cargo.lock | 42 +++++++++++++ zork++/Cargo.toml | 1 + zork++/src/lib/bounds/mod.rs | 36 +++++++++-- zork++/src/lib/cache/mod.rs | 6 +- zork++/src/lib/cli/output/arguments.rs | 2 +- zork++/src/lib/cli/output/commands.rs | 6 +- zork++/src/lib/compiler/mod.rs | 68 ++++++++++++--------- zork++/src/lib/compiler/translation_unit.rs | 8 +++ zork++/src/lib/lib.rs | 4 +- zork++/src/lib/project_model/modules.rs | 48 ++++++++++----- zork++/src/lib/project_model/sourceset.rs | 21 ++++--- 11 files changed, 177 insertions(+), 65 deletions(-) create mode 100644 zork++/src/lib/compiler/translation_unit.rs diff --git a/zork++/Cargo.lock b/zork++/Cargo.lock index 37ffc494..8fd9c1ae 100644 --- a/zork++/Cargo.lock +++ b/zork++/Cargo.lock @@ -824,6 +824,26 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -843,6 +863,27 @@ dependencies = [ "serde", ] +[[package]] +name = "transient" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77decef00c0f222d5a9ea6ca0e274ec277283ea6d27fc2325dedef5c89dfe44" +dependencies = [ + "transient-derive", +] + +[[package]] +name = "transient-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3291c7621de33230fec4b9032dc3939e66efb2bb425b796579d65ec11bccf4d9" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "thiserror", +] + [[package]] name = "typeid" version = "1.0.0" @@ -1088,6 +1129,7 @@ dependencies = [ "serde_json", "tempfile", "toml", + "transient", "typetag", "walkdir", ] diff --git a/zork++/Cargo.toml b/zork++/Cargo.toml index bb01a3b9..76a3e075 100644 --- a/zork++/Cargo.toml +++ b/zork++/Cargo.toml @@ -19,6 +19,7 @@ toml = "0.5.11" glob = "0.3.1" serde = { version = "1.0.202", features = ["derive"] } typetag = "0.2" +transient = "0.4.0" clap = { version = "4.0.32", features = ["derive"] } log = "0.4.17" env_logger = "0.11.3" diff --git a/zork++/src/lib/bounds/mod.rs b/zork++/src/lib/bounds/mod.rs index d97d30c6..d7805336 100644 --- a/zork++/src/lib/bounds/mod.rs +++ b/zork++/src/lib/bounds/mod.rs @@ -5,6 +5,8 @@ use std::borrow::Cow; use std::fmt::Display; use std::path::PathBuf; +use transient::{Any, Inv}; + use crate::{cli::output::arguments::Argument, project_model::sourceset::SourceSet}; /// Bound for the user defined arguments that are passed to the compiler @@ -18,9 +20,35 @@ pub trait ExecutableTarget<'a>: ExtraArgs<'a> { fn sourceset(&'a self) -> &'a SourceSet; } +// Base trait for downcasting +pub trait AsTranslationUnit<'a> { + fn as_any(&self) -> &dyn Any>; +} + +/* pub trait TranslationUnit<'a>: AsTranslationUnit<'a> + Any> { + fn file_stem(&self) -> &Cow<'a, str>; +} */ + +// Implementation of AsTranslationUnit for all types implementing TranslationUnit +impl<'a, T: TranslationUnit<'a> + 'a> AsTranslationUnit<'a> for T { + fn as_any(&self) -> &dyn Any> { + self + } +} + +/* impl<'a, T: TranslationUnit<'a>+ 'a> AsTranslationUnit<'a> for &'a T { + fn as_any(&self) -> &dyn Any> { + self + } +} */ +/* unsafe impl <'a> transient::Transient for &'a dyn TranslationUnit<'a> { + type Static = &'static dyn TranslationUnit<'static>; + type Transience = Inv<'a>; +} */ + /// Represents any kind of translation unit and the generic operations /// applicable to all the implementors -pub trait TranslationUnit: Display + Debug { +pub trait TranslationUnit<'a>: AsTranslationUnit<'a> + Any> + Display + Debug { /// Returns the file, being the addition of the path property plus the file stem plus /// the extension property /// @@ -51,13 +79,13 @@ pub trait TranslationUnit: Display + Debug { fn file(&self) -> PathBuf; /// Outputs the declared path for `self`, being self the translation unit - fn path(&self) -> PathBuf; + fn path(&self) -> &PathBuf; /// Outputs the declared file stem for this translation unit - fn file_stem(&self) -> Cow<'_, str>; + fn file_stem(&self) -> &Cow<'_, str>; /// Outputs the declared extension for `self` - fn extension(&self) -> Cow<'_, str>; + fn extension(&self) -> &Cow<'_, str>; /// Outputs the file stem concatenated with the extension for a given tu fn file_with_extension(&self) -> String { diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index bd10c6cc..45bac2c7 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -105,7 +105,7 @@ impl<'a> ZorkCache<'a> { self.generated_commands .interfaces .iter_mut() - .find(|mi| module_interface_model.file() == (*mi).path()) + .find(|mi| *module_interface_model.file() == (*mi).path()) } pub fn get_module_impl_cmd( @@ -115,10 +115,10 @@ impl<'a> ZorkCache<'a> { self.generated_commands .implementations .iter_mut() - .find(|mi| module_impl_model.file() == (*mi).path()) + .find(|mi| *module_impl_model.file() == (*mi).path()) } - pub fn get_source_cmd( + pub fn get_source_cmd>( &mut self, module_impl_model: &T, ) -> Option<&mut SourceCommandLine> { diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index eafa5c29..552ec09d 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -280,7 +280,7 @@ pub mod msvc_args { use super::Arguments; pub(crate) fn generate_std_cmd( - model: &ZorkModel<'_>, + _model: &ZorkModel<'_>, cache: &ZorkCache, stdlib_mode: StdLibMode, ) -> SourceCommandLine { diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index 693b8ba7..9d79a340 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -168,13 +168,13 @@ pub struct SourceCommandLine { } impl SourceCommandLine { - pub fn new( + pub fn new<'a, T: TranslationUnit<'a>>( // TODO init it as a args holder, but doesn't have the status yet - tu: impl TranslationUnit, + tu: &T, args: Arguments, ) -> Self { Self { - directory: tu.path(), + directory: tu.path().to_path_buf(), filename: tu.file_with_extension(), args, need_to_build: true, diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 1b0b6cc9..6e9328e7 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -29,35 +29,36 @@ use self::data_factory::CommonArgs; /// for every translation unit declared by the user for its project pub fn generate_commands<'a>( model: &'a ZorkModel<'a>, - cache: &mut ZorkCache, + mut cache: ZorkCache<'a>, tests: bool, -) -> Result<()> { +) -> Result> { // TODO: guard it with a presence check */ // They should only be generated the first time or on every cache reset cache.generated_commands.general_args = CommonArgs::from(model); cache.generated_commands.compiler_common_args = - data_factory::compiler_common_arguments_factory(model, cache); + data_factory::compiler_common_arguments_factory(model, &cache); // TODO: add them to the commands DS, so they are together until they're generated // Build the std library as a module - generate_modular_stdlibs_cmds(model, cache); // TODO: ward it with an if for only call this fn for the + generate_modular_stdlibs_cmds(model, &mut cache); // TODO: ward it with an if for only call this fn for the // 1st - Build the modules if let Some(modules) = &model.modules { // TODO: re-think again this optional // Pre-tasks if model.compiler.cpp_compiler == CppCompiler::GCC && !modules.sys_modules.is_empty() { - helpers::build_sys_modules(model, cache) + helpers::build_sys_modules(model, &mut cache) } - process_modules(model, cache)?; + process_modules(model, &mut cache)?; }; + // 2nd - Generate the commands for the non-module sources - generate_sources_cmds_args(model, cache, tests)?; + generate_sources_cmds_args(model, &mut cache, tests)?; // 3rd - Genates the commands for the 'targets' declared by the user - generate_linkage_targets_commands(model, cache, tests)?; + generate_linkage_targets_commands(model, &mut cache, tests)?; - Ok(()) + Ok(cache) } /// Generates the cmds for build the C++ standard libraries (std and std.compat) acording to the specification @@ -84,7 +85,11 @@ fn generate_modular_stdlibs_cmds(model: &ZorkModel<'_>, cache: &mut ZorkCache) { ); cpp_stdlib_cmd.need_to_build = build_std; - if !build_std && !cpp_stdlib_cmd.execution_result.eq(&CommandExecutionResult::Cached) { + if !build_std + && !cpp_stdlib_cmd + .execution_result + .eq(&CommandExecutionResult::Cached) + { cpp_stdlib_cmd.execution_result = CommandExecutionResult::Cached; } else { cpp_stdlib_cmd.execution_result = CommandExecutionResult::PendingToBuild; @@ -114,7 +119,11 @@ fn generate_modular_stdlibs_cmds(model: &ZorkModel<'_>, cache: &mut ZorkCache) { ); ccompat_stdlib_cmd.need_to_build = build_ccompat; - if !build_ccompat && !ccompat_stdlib_cmd.execution_result.eq(&CommandExecutionResult::Cached) { + if !build_ccompat + && !ccompat_stdlib_cmd + .execution_result + .eq(&CommandExecutionResult::Cached) + { ccompat_stdlib_cmd.execution_result = CommandExecutionResult::Cached; } else { ccompat_stdlib_cmd.execution_result = CommandExecutionResult::PendingToBuild; @@ -135,7 +144,11 @@ fn generate_modular_stdlibs_cmds(model: &ZorkModel<'_>, cache: &mut ZorkCache) { /// Legacy: /// If this flow is enabled by the Cli arg `Tests`, then the executable will be generated /// for the files and properties declared for the tests section in the configuration file -fn generate_linkage_targets_commands(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> Result<()> { +fn generate_linkage_targets_commands<'a>( + model: &'a ZorkModel<'_>, + cache: &'a mut ZorkCache<'_>, + tests: bool, +) -> Result<()> { // TODO: Check if the command line is the same as the previous? If there's no new sources? // And avoid re-executing? // TODO: refactor this code, just having the if-else branch inside the fn @@ -149,7 +162,11 @@ fn generate_linkage_targets_commands(model: &ZorkModel<'_>, cache: &mut ZorkCach } } -fn generate_sources_cmds_args(model: &ZorkModel<'_>, cache: &mut ZorkCache, tests: bool) -> Result<()> { +fn generate_sources_cmds_args<'a>( + model: &ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + tests: bool, +) -> Result<()> { log::info!("Generating the commands for the source files..."); let (srcs, target_kind) = if tests { ( @@ -175,9 +192,7 @@ fn generate_sources_cmds_args(model: &ZorkModel<'_>, cache: &mut ZorkCache, test } generated_cmd.need_to_build = translation_unit_must_be_rebuilt; } else { - sources::generate_sources_arguments(model, cache, target_kind, source) // TODO: wtf is - // the - // model.tests?? + sources::generate_sources_arguments(model, cache, target_kind, source) }; }); @@ -259,8 +274,8 @@ fn process_module_implementations<'a>( /// Generates the command line arguments for the desired target pub fn generate_linker_command_line_args<'a>( - model: &'a ZorkModel, - cache: &mut ZorkCache, + model: &ZorkModel<'_>, + cache: &mut ZorkCache<'_>, target: &'a impl ExecutableTarget<'a>, ) -> Result<()> { log::info!("Generating the linker command line..."); @@ -347,15 +362,12 @@ mod sources { use crate::bounds::ExtraArgs; use crate::cache::ZorkCache; use crate::cli::output::arguments::Arguments; + use crate::project_model::modules::ModuleImplementationModel; use crate::project_model::sourceset::SourceFile; use crate::{ bounds::{ExecutableTarget, TranslationUnit}, cli::output::{arguments::clang_args, commands::SourceCommandLine}, - project_model::{ - compiler::CppCompiler, - modules::{ModuleImplementationModel, ModuleInterfaceModel}, - ZorkModel, - }, + project_model::{compiler::CppCompiler, modules::ModuleInterfaceModel, ZorkModel}, }; /// Generates the command line arguments for non-module source files @@ -560,7 +572,7 @@ mod sources { .join(compiler.as_ref()) .join("modules") .join("implementations") - .join::<&str>(&implementation.file_stem()) + .join::<&str>(implementation.file_stem()) .with_extension(compiler.get_obj_file_extension()); cache @@ -582,7 +594,7 @@ mod sources { } } - let cmd = SourceCommandLine::new(implementation, arguments); + let cmd = SourceCommandLine::new(implementation.to_owned(), arguments); cache.generated_commands.implementations.push(cmd); } } @@ -624,7 +636,7 @@ mod helpers { if !partition.partition_name.is_empty() { temp.push_str(&partition.partition_name) } else { - temp.push_str(&interface.file_stem()) + temp.push_str(interface.file_stem()) } } else { temp.push_str(&interface.module_name) @@ -657,7 +669,7 @@ mod helpers { .join(compiler.as_ref()) .join("modules") .join("implementations") - .join::<&str>(&*implementation.file_stem()) + .join::<&str>(implementation.file_stem()) .with_extension(compiler.get_obj_file_extension()) } @@ -797,7 +809,7 @@ mod helpers { out_dir .join(compiler.as_ref()) .join("sources") - .join::<&str>(&*source.file_stem()) + .join::<&str>(source.file_stem()) .with_extension(compiler.get_obj_file_extension()) } } diff --git a/zork++/src/lib/compiler/translation_unit.rs b/zork++/src/lib/compiler/translation_unit.rs new file mode 100644 index 00000000..31881abf --- /dev/null +++ b/zork++/src/lib/compiler/translation_unit.rs @@ -0,0 +1,8 @@ + + +pub enum TranslationUnitKind { + ModuleInterface, + ModuleImplementation, + SourceFile, + HeaderFile +} diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 18e8f71c..b3e3fa1f 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -107,11 +107,11 @@ pub mod worker { fn do_main_work_based_on_cli_input<'a>( cli_args: &'a CliArgs, program_data: &'a ZorkModel<'_>, - mut cache: ZorkCache, + cache: ZorkCache, ) -> Result { let is_tests_run = cli_args.command.eq(&Command::Test); - generate_commands(program_data, &mut cache, false) + let mut cache = generate_commands(program_data, cache, false) .with_context(|| "Failed to generated the commands for the project")?; let execution_result = match cli_args.command { diff --git a/zork++/src/lib/project_model/modules.rs b/zork++/src/lib/project_model/modules.rs index 203ef2f0..fddb8887 100644 --- a/zork++/src/lib/project_model/modules.rs +++ b/zork++/src/lib/project_model/modules.rs @@ -2,6 +2,8 @@ use core::fmt; use std::borrow::Cow; use std::path::{Path, PathBuf}; +use transient::Transient; + use crate::bounds::ExtraArgs; use crate::cli::output::arguments::Argument; use crate::{bounds::TranslationUnit, config_file::modules::ModulePartition}; @@ -22,7 +24,7 @@ impl<'a> ExtraArgs<'a> for ModulesModel<'a> { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Transient)] pub struct ModuleInterfaceModel<'a> { pub path: PathBuf, pub file_stem: Cow<'a, str>, @@ -42,26 +44,26 @@ impl<'a> fmt::Display for ModuleInterfaceModel<'a> { } } -impl<'a> TranslationUnit for ModuleInterfaceModel<'a> { +impl<'a> TranslationUnit<'a> for ModuleInterfaceModel<'a> { fn file(&self) -> PathBuf { let file_name = format!("{}.{}", self.file_stem, self.extension); self.path().join(file_name) } - fn path(&self) -> PathBuf { - self.path.clone() + fn path(&self) -> &PathBuf { + &self.path } - fn file_stem(&self) -> Cow<'_, str> { - self.file_stem.clone() + fn file_stem(&self) -> &Cow<'_, str> { + &self.file_stem } - fn extension(&self) -> Cow<'_, str> { - self.extension.clone() + fn extension(&self) -> &Cow<'_, str> { + &self.extension } } -impl<'a> TranslationUnit for &'a ModuleInterfaceModel<'a> { +/* impl<'a> TranslationUnit<'a> for &'a ModuleInterfaceModel<'a> { fn file(&self) -> PathBuf { let file_name = format!("{}.{}", self.file_stem, self.extension); self.path().join(file_name) @@ -78,9 +80,9 @@ impl<'a> TranslationUnit for &'a ModuleInterfaceModel<'a> { fn extension(&self) -> Cow<'a, str> { self.extension.clone() } -} +} */ -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Transient, Clone)] pub struct ModulePartitionModel<'a> { pub module: Cow<'a, str>, pub partition_name: Cow<'a, str>, @@ -107,7 +109,7 @@ impl<'a> From> for ModulePartitionModel<'a> { // } // } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Transient)] pub struct ModuleImplementationModel<'a> { pub path: PathBuf, pub file_stem: Cow<'a, str>, @@ -121,7 +123,25 @@ impl<'a> fmt::Display for ModuleImplementationModel<'a> { } } -impl<'a> TranslationUnit for &'a ModuleImplementationModel<'a> { +impl<'a> TranslationUnit<'a> for ModuleImplementationModel<'a> { + fn file(&self) -> PathBuf { + let file_name = format!("{}.{}", self.file_stem, self.extension); + self.path().join(file_name) + } + + fn path(&self) -> &PathBuf { + &self.path + } + + fn file_stem(&self) -> &Cow<'_, str> { + &self.file_stem + } + + fn extension(&self) -> &Cow<'_, str> { + &self.extension + } +} +/* impl<'a> TranslationUnit<'a> for &'a ModuleImplementationModel<'a> { fn file(&self) -> PathBuf { let file_name = format!("{}.{}", self.file_stem, self.extension); self.path().join(file_name) @@ -138,4 +158,4 @@ impl<'a> TranslationUnit for &'a ModuleImplementationModel<'a> { fn extension(&self) -> Cow<'_, str> { self.extension.clone() } -} +} */ diff --git a/zork++/src/lib/project_model/sourceset.rs b/zork++/src/lib/project_model/sourceset.rs index 6761be4d..ab71a330 100644 --- a/zork++/src/lib/project_model/sourceset.rs +++ b/zork++/src/lib/project_model/sourceset.rs @@ -5,6 +5,7 @@ use std::path::{Path, PathBuf}; use crate::bounds::TranslationUnit; use color_eyre::{eyre::Context, Result}; use serde::{Deserialize, Serialize}; +use transient::Transient; use crate::cli::output::arguments::Argument; @@ -29,33 +30,33 @@ impl File for PathBuf { // TODO: All the trait File impl as well as the trait aren't required anymore -#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize, Transient)] pub struct SourceFile<'a> { pub path: PathBuf, pub file_stem: Cow<'a, str>, pub extension: Cow<'a, str>, } -impl<'a> TranslationUnit for SourceFile<'a> { +impl<'a> TranslationUnit<'a> for SourceFile<'a> { fn file(&self) -> PathBuf { let file_name = format!("{}.{}", self.file_stem, self.extension); self.path().join(file_name) } - fn path(&self) -> PathBuf { - self.path.clone() + fn path(&self) -> &PathBuf { + &self.path } - fn file_stem(&self) -> Cow<'_, str> { - self.file_stem.clone() + fn file_stem(&self) -> &Cow<'_, str> { + &self.file_stem } - fn extension(&self) -> Cow<'_, str> { - self.extension.clone() + fn extension(&self) -> &Cow<'_, str> { + &self.extension } } -impl<'a> TranslationUnit for &'a SourceFile<'a> { +/* impl<'a> TranslationUnit<'a> for &'a SourceFile<'a> { fn file(&self) -> PathBuf { let file_name = format!("{}.{}", self.file_stem, self.extension); self.path().join(file_name) @@ -73,7 +74,7 @@ impl<'a> TranslationUnit for &'a SourceFile<'a> { self.extension.clone() } } - +*/ impl<'a> fmt::Display for SourceFile<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( From 56257d3c072cd70340b0ff5dd33d490c27b95bb0 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 5 Jul 2024 12:50:42 +0200 Subject: [PATCH 25/73] feat: Generification of the commands generator for any kind of Translation Unit, so now we only need a single block of code to process them all --- zork++/src/lib/bounds/mod.rs | 14 -- zork++/src/lib/cache/mod.rs | 27 +++- zork++/src/lib/cli/output/arguments.rs | 2 +- zork++/src/lib/compiler/mod.rs | 170 ++++++++++++------------- zork++/src/lib/lib.rs | 2 +- 5 files changed, 105 insertions(+), 110 deletions(-) diff --git a/zork++/src/lib/bounds/mod.rs b/zork++/src/lib/bounds/mod.rs index d7805336..2535d2f6 100644 --- a/zork++/src/lib/bounds/mod.rs +++ b/zork++/src/lib/bounds/mod.rs @@ -25,10 +25,6 @@ pub trait AsTranslationUnit<'a> { fn as_any(&self) -> &dyn Any>; } -/* pub trait TranslationUnit<'a>: AsTranslationUnit<'a> + Any> { - fn file_stem(&self) -> &Cow<'a, str>; -} */ - // Implementation of AsTranslationUnit for all types implementing TranslationUnit impl<'a, T: TranslationUnit<'a> + 'a> AsTranslationUnit<'a> for T { fn as_any(&self) -> &dyn Any> { @@ -36,16 +32,6 @@ impl<'a, T: TranslationUnit<'a> + 'a> AsTranslationUnit<'a> for T { } } -/* impl<'a, T: TranslationUnit<'a>+ 'a> AsTranslationUnit<'a> for &'a T { - fn as_any(&self) -> &dyn Any> { - self - } -} */ -/* unsafe impl <'a> transient::Transient for &'a dyn TranslationUnit<'a> { - type Static = &'static dyn TranslationUnit<'static>; - type Transience = Inv<'a>; -} */ - /// Represents any kind of translation unit and the generic operations /// applicable to all the implementors pub trait TranslationUnit<'a>: AsTranslationUnit<'a> + Any> + Display + Debug { diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 45bac2c7..c192db8f 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -16,7 +16,8 @@ use std::{ use crate::bounds::TranslationUnit; use crate::cache::compile_commands::CompileCommands; -use crate::project_model::modules::{ModuleImplementationModel, ModuleInterfaceModel}; +use crate::compiler::TranslationUnitKind; + use crate::project_model::sourceset::SourceFile; use crate::{ cli::{ @@ -98,9 +99,23 @@ impl<'a> ZorkCache<'a> { &self.last_program_execution } - pub fn get_module_ifc_cmd( + pub fn get_cmd_for_translation_unit_kind>( &mut self, - module_interface_model: &ModuleInterfaceModel, + translation_unit: &T, + translation_unit_kind: &TranslationUnitKind, + ) -> Option<&mut SourceCommandLine> { + return match translation_unit_kind { + TranslationUnitKind::ModuleInterface => self.get_module_ifc_cmd(translation_unit), + TranslationUnitKind::ModuleImplementation => self.get_module_impl_cmd(translation_unit), + TranslationUnitKind::SourceFile => self.get_source_cmd(translation_unit), + TranslationUnitKind::ModularStdLib => todo!(), + TranslationUnitKind::HeaderFile => todo!(), + }; + } + + fn get_module_ifc_cmd>( + &mut self, + module_interface_model: &T, ) -> Option<&mut SourceCommandLine> { self.generated_commands .interfaces @@ -108,9 +123,9 @@ impl<'a> ZorkCache<'a> { .find(|mi| *module_interface_model.file() == (*mi).path()) } - pub fn get_module_impl_cmd( + fn get_module_impl_cmd>( &mut self, - module_impl_model: &ModuleImplementationModel, + module_impl_model: &T, ) -> Option<&mut SourceCommandLine> { self.generated_commands .implementations @@ -118,7 +133,7 @@ impl<'a> ZorkCache<'a> { .find(|mi| *module_impl_model.file() == (*mi).path()) } - pub fn get_source_cmd>( + fn get_source_cmd>( &mut self, module_impl_model: &T, ) -> Option<&mut SourceCommandLine> { diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index 552ec09d..b8b6f7eb 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -280,7 +280,7 @@ pub mod msvc_args { use super::Arguments; pub(crate) fn generate_std_cmd( - _model: &ZorkModel<'_>, + _model: &ZorkModel<'_>, // TODO: ensure and then remove cache: &ZorkCache, stdlib_mode: StdLibMode, ) -> SourceCommandLine { diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 6e9328e7..2eeb2495 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -9,9 +9,11 @@ use color_eyre::Result; use std::path::Path; use crate::bounds::{ExecutableTarget, TranslationUnit}; +use crate::cli::input::{CliArgs, Command}; use crate::cli::output::arguments::{clang_args, msvc_args, Arguments}; use crate::cli::output::commands::{CommandExecutionResult, SourceCommandLine}; use crate::project_model::compiler::StdLibMode; +use crate::project_model::sourceset::SourceFile; use crate::utils::constants; use crate::{ cache::ZorkCache, @@ -30,7 +32,7 @@ use self::data_factory::CommonArgs; pub fn generate_commands<'a>( model: &'a ZorkModel<'a>, mut cache: ZorkCache<'a>, - tests: bool, + cli_args: &'a CliArgs, ) -> Result> { // TODO: guard it with a presence check */ // They should only be generated the first time or on every cache reset @@ -50,13 +52,13 @@ pub fn generate_commands<'a>( helpers::build_sys_modules(model, &mut cache) } - process_modules(model, &mut cache)?; + process_modules(model, &mut cache, cli_args)?; }; // 2nd - Generate the commands for the non-module sources - generate_sources_cmds_args(model, &mut cache, tests)?; + generate_sources_cmds_args(model, &mut cache, cli_args)?; // 3rd - Genates the commands for the 'targets' declared by the user - generate_linkage_targets_commands(model, &mut cache, tests)?; + generate_linkage_targets_commands(model, &mut cache, cli_args)?; Ok(cache) } @@ -139,7 +141,36 @@ fn generate_modular_stdlibs_cmds(model: &ZorkModel<'_>, cache: &mut ZorkCache) { } } +fn process_modules<'a>( + model: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + cli_args: &'a CliArgs, +) -> Result<()> { + let modules = model.modules.as_ref().unwrap(); // TODO: review this opt again + + log::info!("Generating the commands for the module interfaces and partitions..."); + process_kind_translation_units( + model, + cache, + cli_args, + &modules.interfaces, + TranslationUnitKind::ModuleInterface, + ); + + log::info!("Generating the commands for the module implementations and partitions..."); + process_kind_translation_units( + model, + cache, + cli_args, + &modules.implementations, + TranslationUnitKind::ModuleImplementation, + ); + + Ok(()) +} + /// Generates the command line that will be passed to the linker to generate an [`ExecutableTarget`] +/// Generates the commands for the C++ modules declared in the project /// /// Legacy: /// If this flow is enabled by the Cli arg `Tests`, then the executable will be generated @@ -147,14 +178,15 @@ fn generate_modular_stdlibs_cmds(model: &ZorkModel<'_>, cache: &mut ZorkCache) { fn generate_linkage_targets_commands<'a>( model: &'a ZorkModel<'_>, cache: &'a mut ZorkCache<'_>, - tests: bool, + cli_args: &'a CliArgs, ) -> Result<()> { // TODO: Check if the command line is the same as the previous? If there's no new sources? // And avoid re-executing? // TODO: refactor this code, just having the if-else branch inside the fn // Also, shouldn't we start to think about named targets? So introduce the static and dynamic // libraries wouldn't be such a pain? - if tests { + let is_tests_run = cli_args.command.eq(&Command::Test); + if is_tests_run { generate_linker_command_line_args(model, cache, &model.tests) // TODO: shouldn't tests be // just a target? } else { @@ -163,111 +195,73 @@ fn generate_linkage_targets_commands<'a>( } fn generate_sources_cmds_args<'a>( - model: &ZorkModel<'a>, + model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, - tests: bool, + cli_args: &'a CliArgs, ) -> Result<()> { log::info!("Generating the commands for the source files..."); - let (srcs, target_kind) = if tests { - ( - &model.tests.sourceset.sources, - &model.tests as &dyn ExecutableTarget, - ) + // TODO: tests manual run must be start to be deprecated in favour of the future + // named targets, so we won't mess now with them + let is_tests_run = cli_args.command.eq(&Command::Test); + let srcs = if is_tests_run { + &model.tests.sourceset.sources } else { - ( - &model.executable.sourceset.sources, - &model.executable as &dyn ExecutableTarget, - ) + &model.executable.sourceset.sources }; - let compiler = cache.compiler; - let lpe = cache.last_program_execution; - - srcs.iter().for_each(|source| { - if let Some(generated_cmd) = cache.get_source_cmd(source) { - let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_built(compiler, &lpe, generated_cmd, &source.file()); - - if !translation_unit_must_be_rebuilt { - log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &source.file()); - } - generated_cmd.need_to_build = translation_unit_must_be_rebuilt; - } else { - sources::generate_sources_arguments(model, cache, target_kind, source) - }; - }); + process_kind_translation_units( + model, + cache, + cli_args, + srcs, + TranslationUnitKind::SourceFile, + ); Ok(()) } -/// Generates the commands for the C++ modules declared in the project -fn process_modules(model: &ZorkModel, cache: &mut ZorkCache) -> Result<()> { - log::info!("Generating the commands for the module interfaces and partitions..."); - - let modules = model.modules.as_ref().unwrap(); // TODO: review this opt again - - process_module_interfaces(model, cache, &modules.interfaces); - - // TODO: find a way to avoid duplicating the implementations of both kind of translation units commands generation, since they - // only differs on the call that are generated - // Alternatives: self as any?, maybe a translation unit kind? knowing the call, primitive cast of - // an slice of trait objects? - - // TODO: make a trait exclusive for ModuleTranslationUnit: TranslationUnit, since the operations required - // are mostly paths and add dependencies -> &[Cow<'_, str>] - - log::info!("Generating the commands for the module implementations and partitions..."); - process_module_implementations(model, cache, &modules.implementations); - - Ok(()) +pub enum TranslationUnitKind { + ModuleInterface, + ModuleImplementation, + SourceFile, + HeaderFile, + ModularStdLib, } -/// Parses the configuration in order to build the BMIs declared for the project, -/// by pre-compiling the module interface units -fn process_module_interfaces<'a>( - model: &'a ZorkModel<'_>, - cache: &mut ZorkCache, - interfaces: &'a [ModuleInterfaceModel], +fn process_kind_translation_units<'a, T: TranslationUnit<'a>>( + model: &ZorkModel<'_>, + cache: &mut ZorkCache<'a>, + cli_args: &CliArgs, + translation_units: &'a [T], + for_kind: TranslationUnitKind, ) { - // let c: Vec<&dyn TranslationUnit> = interfaces.iter().map(|mi| mi as &dyn TranslationUnit).collect(); - // let d = c.iter().map(|tu| tu as &dyn ModuleInterfaceModel).collect(); // downcast, as isn't usable for non primitives let compiler = cache.compiler; let lpe = cache.last_program_execution; - interfaces.iter().for_each(|module_interface| { - if let Some(generated_cmd) = cache.get_module_ifc_cmd(module_interface) { - let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_built(compiler, &lpe, generated_cmd, &module_interface.file()); + translation_units.iter().for_each(|translation_unit| { + if let Some(generated_cmd) = cache.get_cmd_for_translation_unit_kind(translation_unit, &for_kind) { + let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_built(compiler, &lpe, generated_cmd, &translation_unit.file()); if !translation_unit_must_be_rebuilt { - log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &module_interface.file()); + log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &translation_unit.file()); // TODO: here is where we should use the normalize_execution_result_status // function, to mark executions as cached (when tu musn't be rebuilt) } generated_cmd.need_to_build = translation_unit_must_be_rebuilt; } else { - sources::generate_module_interface_cmd(model, cache, module_interface) - }; - }); -} - -/// Generates the commands for every [`ModuleImplementationModel`] -fn process_module_implementations<'a>( - model: &'a ZorkModel, - cache: &mut ZorkCache, - impls: &'a [ModuleImplementationModel], -) { - let compiler = cache.compiler; - let lpe = cache.last_program_execution; // TODO: check for an Rc< or other solution, but closure below requires unique access - - impls.iter().for_each(|module_impl| { - if let Some(generated_cmd) = cache.get_module_impl_cmd(module_impl) { - let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_built(compiler, &lpe, generated_cmd, &module_impl.file()); - - if !translation_unit_must_be_rebuilt { - log::trace!("Source file: {:?} was not modified since the last iteration. No need to rebuilt it again.", &module_impl.file()); + let tu_with_erased_type = translation_unit.as_any(); + match for_kind { + TranslationUnitKind::ModuleInterface => { + let resolved_tu = transient::Downcast::downcast_ref::(tu_with_erased_type); + sources::generate_module_interface_cmd(model, cache, resolved_tu.unwrap()); + }, + TranslationUnitKind::ModuleImplementation => sources::generate_module_implementation_cmd(model, cache, transient::Downcast::downcast_ref::(tu_with_erased_type).unwrap()), + TranslationUnitKind::SourceFile => { + let target = if cli_args.command.eq(&Command::Test) { &model.tests as &dyn ExecutableTarget } else { &model.executable as &dyn ExecutableTarget }; + sources::generate_sources_arguments(model, cache, transient::Downcast::downcast_ref::(tu_with_erased_type).unwrap(), target) + } + _ => todo!() } - generated_cmd.need_to_build = translation_unit_must_be_rebuilt; - } else { - sources::generate_module_implementation_cmd(model, cache, module_impl) }; }); } @@ -374,8 +368,8 @@ mod sources { pub fn generate_sources_arguments<'a>( model: &'a ZorkModel, cache: &mut ZorkCache, - target: &'a (impl ExecutableTarget<'a> + ?Sized), source: &'a SourceFile, + target: &'a (impl ExecutableTarget<'a> + ?Sized), ) { let compiler = model.compiler.cpp_compiler; let out_dir = model.build.output_dir.as_ref(); diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index b3e3fa1f..1cf987ba 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -111,7 +111,7 @@ pub mod worker { ) -> Result { let is_tests_run = cli_args.command.eq(&Command::Test); - let mut cache = generate_commands(program_data, cache, false) + let mut cache = generate_commands(program_data, cache, cli_args) .with_context(|| "Failed to generated the commands for the project")?; let execution_result = match cli_args.command { From 215cea714fcddd7ec97e3799787188a6d7cea20c Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 8 Jul 2024 10:47:23 +0200 Subject: [PATCH 26/73] feat: Avoiding duplicating the linker arguments with an early guard while we don't implement named targets --- zork++/src/lib/cli/output/arguments.rs | 5 +++ zork++/src/lib/cli/output/commands.rs | 15 ++++++- zork++/src/lib/compiler/data_factory.rs | 12 +----- zork++/src/lib/compiler/mod.rs | 56 +++++++++---------------- 4 files changed, 39 insertions(+), 49 deletions(-) diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index b8b6f7eb..e7ae684e 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -159,6 +159,11 @@ impl Arguments { pub fn as_slice(&self) -> &[Argument] { &self.0 } + + /// Clears the contained values of the wrapped [`std::vec::Vec`] + pub fn clear(&mut self) { + self.0.clear() + } } impl Deref for Arguments { diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index 9d79a340..72b262a0 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -52,11 +52,18 @@ pub fn run_generated_commands( .filter(|scl| scl.need_to_build) .collect::>(); + let compile_but_dont_link: [Argument; 1] = + [Argument::from(match program_data.compiler.cpp_compiler { + CppCompiler::CLANG | CppCompiler::GCC => "-c", + CppCompiler::MSVC => "/c", + })]; + for translation_unit_cmd in translation_units { // Join the concrete args of any translation unit with the ones held in the flyweights let translation_unit_cmd_args: Arguments = general_args .iter() .chain(compiler_specific_shared_args.iter()) + .chain(&compile_but_dont_link) .chain(translation_unit_cmd.args.iter()) .collect(); @@ -79,7 +86,11 @@ pub fn run_generated_commands( let r = execute_command( program_data, - &cache.generated_commands.linker.args, + &general_args + .iter() + .chain(compiler_specific_shared_args.iter()) + .chain(cache.generated_commands.linker.args.iter()) + .collect::(), &env_args, ); @@ -135,7 +146,7 @@ fn execute_command( env_vars: &EnvVars, ) -> Result where - T: IntoIterator + std::fmt::Display + std::marker::Copy, + T: IntoIterator + std::fmt::Display + Copy, S: AsRef, { let compiler = model.compiler.cpp_compiler; diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index 06dec60f..19d7bf05 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -107,8 +107,6 @@ impl CompilerCommonArguments for MsvcCommonArgs { let mut args = Arguments::default(); args.create_and_push(&self.exception_handling_model); args.create_and_push(&self.no_logo); - args.create_and_push(&self.no_compile); - args.create_and_push(&self.ifc_search_dir); args.create_and_push(&*self.ifc_search_dir_value); @@ -128,17 +126,13 @@ impl CompilerCommonArguments for GccCommonArgs { fn get_args(&self) -> Arguments { let mut args = Arguments::default(); args.create_and_push("-fmodules-ts"); - args.create_and_push("-c"); args } } #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct ClangCommonArgs { - // TODO: a HashMap per kind of translation unit which stores the common ones - // for every different kind of translation unit - std_lib: StdLib, // TODO: technically, should this already be an arg? or should we decouple the - // project model for the Argument(s) type(s)? + std_lib: StdLib, implicit_modules: Cow<'static, str>, implicit_module_map: Argument, prebuilt_module_path: Cow<'static, str>, @@ -163,7 +157,6 @@ impl ClangCommonArgs { pub struct MsvcCommonArgs { exception_handling_model: Cow<'static, str>, no_logo: Cow<'static, str>, - no_compile: Cow<'static, str>, reference: Cow<'static, str>, ifc_search_dir: Cow<'static, str>, ifc_search_dir_value: Cow<'static, Path>, @@ -177,7 +170,6 @@ impl MsvcCommonArgs { Self { exception_handling_model: Cow::Borrowed("/EHsc"), no_logo: Cow::Borrowed("/nologo"), - no_compile: Cow::Borrowed("/c"), reference: Cow::Borrowed("/reference"), ifc_search_dir: Cow::Borrowed("/ifcSearchDir"), @@ -197,13 +189,11 @@ impl MsvcCommonArgs { #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct GccCommonArgs { - compile_but_dont_link: Cow<'static, str>, modules_ts: Cow<'static, str>, } impl GccCommonArgs { pub fn new() -> Self { Self { - compile_but_dont_link: Cow::Borrowed("-c"), modules_ts: Cow::Borrowed("-fmodules-ts"), } } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 2eeb2495..ba50a843 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -10,7 +10,7 @@ use std::path::Path; use crate::bounds::{ExecutableTarget, TranslationUnit}; use crate::cli::input::{CliArgs, Command}; -use crate::cli::output::arguments::{clang_args, msvc_args, Arguments}; +use crate::cli::output::arguments::{msvc_args, Arguments}; use crate::cli::output::commands::{CommandExecutionResult, SourceCommandLine}; use crate::project_model::compiler::StdLibMode; use crate::project_model::sourceset::SourceFile; @@ -71,7 +71,7 @@ fn generate_modular_stdlibs_cmds(model: &ZorkModel<'_>, cache: &mut ZorkCache) { // TODO: remaining ones: Clang, GCC if compiler.eq(&CppCompiler::MSVC) { - let vs_stdlib_path = &cache + let vs_stdlib_path = cache .compilers_metadata .msvc .vs_stdlib_path @@ -105,7 +105,7 @@ fn generate_modular_stdlibs_cmds(model: &ZorkModel<'_>, cache: &mut ZorkCache) { cache.generated_commands.cpp_stdlib = Some(cpp_stdlib); } - let vs_ccompat_stdlib_path = &cache + let vs_ccompat_stdlib_path = cache .compilers_metadata .msvc .vs_c_stdlib_path @@ -180,17 +180,13 @@ fn generate_linkage_targets_commands<'a>( cache: &'a mut ZorkCache<'_>, cli_args: &'a CliArgs, ) -> Result<()> { - // TODO: Check if the command line is the same as the previous? If there's no new sources? - // And avoid re-executing? - // TODO: refactor this code, just having the if-else branch inside the fn - // Also, shouldn't we start to think about named targets? So introduce the static and dynamic + // TODO: Shouldn't we start to think about named targets? So introduce the static and dynamic // libraries wouldn't be such a pain? let is_tests_run = cli_args.command.eq(&Command::Test); if is_tests_run { - generate_linker_command_line_args(model, cache, &model.tests) // TODO: shouldn't tests be - // just a target? + generate_linker_general_command_line_args(model, cache, &model.tests) } else { - generate_linker_command_line_args(model, cache, &model.executable) + generate_linker_general_command_line_args(model, cache, &model.executable) } } @@ -266,14 +262,24 @@ fn process_kind_translation_units<'a, T: TranslationUnit<'a>>( }); } -/// Generates the command line arguments for the desired target -pub fn generate_linker_command_line_args<'a>( +/// Generates the general command line arguments for the desired target +/// +/// **implementation note:** All the final byproducts of the compiled translation units, the object +/// files paths, are added in place to the linker args member when they're created, so we can avoid +/// to clone them everytime we create a new [`SourceCommandLine`] for a given translation unit +pub fn generate_linker_general_command_line_args<'a>( model: &ZorkModel<'_>, cache: &mut ZorkCache<'_>, target: &'a impl ExecutableTarget<'a>, ) -> Result<()> { log::info!("Generating the linker command line..."); + // NOTE: this early guard is provisional while we don't implement the feature of really having + // named targets for each user declared one (instead of the actual run/tests kind of thing) + if cache.generated_commands.linker.args.is_empty() { + return Ok(()); + } + let compiler = &model.compiler.cpp_compiler; let out_dir: &Path = model.build.output_dir.as_ref(); let executable_name = target.name(); @@ -283,19 +289,6 @@ pub fn generate_linker_command_line_args<'a>( match compiler { CppCompiler::CLANG => { - arguments.push_opt(model.compiler.stdlib_arg()); - arguments.create_and_push("-fimplicit-modules"); - arguments.push(clang_args::implicit_module_map(out_dir)); - - arguments.create_and_push(format!( - "-fprebuilt-module-path={}", - out_dir - .join(compiler.as_ref()) - .join("modules") - .join("interfaces") - .display() - )); - arguments.create_and_push("-o"); arguments.create_and_push(format!( "{}", @@ -307,15 +300,6 @@ pub fn generate_linker_command_line_args<'a>( )); } CppCompiler::MSVC => { - arguments.create_and_push("/EHsc"); - arguments.create_and_push("/nologo"); - arguments.create_and_push("/ifcSearchDir"); - arguments.create_and_push( - out_dir - .join(compiler.as_ref()) - .join("modules") - .join("interfaces"), - ); arguments.create_and_push(format!( "/Fo{}\\", out_dir.join(compiler.as_ref()).display() @@ -330,7 +314,6 @@ pub fn generate_linker_command_line_args<'a>( )); } CppCompiler::GCC => { - arguments.create_and_push("-fmodules-ts"); arguments.create_and_push("-o"); arguments.create_and_push(format!( "{}", @@ -343,7 +326,8 @@ pub fn generate_linker_command_line_args<'a>( } }; - cache.generated_commands.linker.args.extend(arguments); + arguments.extend(&cache.generated_commands.linker.args); + cache.generated_commands.linker.args = arguments; Ok(()) } From 6ae32acb5fba8bd5aa59408de9e8788ad9cd5ac0 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 9 Jul 2024 19:42:43 +0200 Subject: [PATCH 27/73] feat: refactor of the core of the procedure for that takes care about the commands generation feat: reworked the cache loading/save feat: added a metadata field for the cache feat: improving the iteration times among different build processes --- zork++/src/lib/bounds/mod.rs | 80 --- zork++/src/lib/cache/compile_commands.rs | 15 +- zork++/src/lib/cache/mod.rs | 259 +++------ zork++/src/lib/cli/output/arguments.rs | 40 +- zork++/src/lib/cli/output/commands.rs | 204 +++++--- zork++/src/lib/compiler/data_factory.rs | 18 +- zork++/src/lib/compiler/mod.rs | 548 ++++++++------------ zork++/src/lib/compiler/translation_unit.rs | 8 - zork++/src/lib/config_file/modules.rs | 8 +- zork++/src/lib/domain/mod.rs | 2 + zork++/src/lib/domain/target.rs | 14 + zork++/src/lib/domain/translation_unit.rs | 97 ++++ zork++/src/lib/lib.rs | 110 ++-- zork++/src/lib/project_model/compiler.rs | 3 +- zork++/src/lib/project_model/executable.rs | 5 +- zork++/src/lib/project_model/mod.rs | 2 +- zork++/src/lib/project_model/modules.rs | 104 +--- zork++/src/lib/project_model/sourceset.rs | 66 +-- zork++/src/lib/project_model/tests.rs | 2 +- zork++/src/lib/utils/constants.rs | 33 +- zork++/src/lib/utils/fs.rs | 12 +- zork++/src/lib/utils/reader.rs | 29 +- zork++/src/lib/utils/template/mod.rs | 2 +- 23 files changed, 701 insertions(+), 960 deletions(-) delete mode 100644 zork++/src/lib/bounds/mod.rs delete mode 100644 zork++/src/lib/compiler/translation_unit.rs create mode 100644 zork++/src/lib/domain/mod.rs create mode 100644 zork++/src/lib/domain/target.rs create mode 100644 zork++/src/lib/domain/translation_unit.rs diff --git a/zork++/src/lib/bounds/mod.rs b/zork++/src/lib/bounds/mod.rs deleted file mode 100644 index 2535d2f6..00000000 --- a/zork++/src/lib/bounds/mod.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! The higher abstractions of the program - -use core::fmt::Debug; -use std::borrow::Cow; -use std::fmt::Display; -use std::path::PathBuf; - -use transient::{Any, Inv}; - -use crate::{cli::output::arguments::Argument, project_model::sourceset::SourceSet}; - -/// Bound for the user defined arguments that are passed to the compiler -pub trait ExtraArgs<'a> { - fn extra_args(&'a self) -> &'a [Argument]; -} - -/// Contracts for the executable operations -pub trait ExecutableTarget<'a>: ExtraArgs<'a> { - fn name(&'a self) -> &'a str; - fn sourceset(&'a self) -> &'a SourceSet; -} - -// Base trait for downcasting -pub trait AsTranslationUnit<'a> { - fn as_any(&self) -> &dyn Any>; -} - -// Implementation of AsTranslationUnit for all types implementing TranslationUnit -impl<'a, T: TranslationUnit<'a> + 'a> AsTranslationUnit<'a> for T { - fn as_any(&self) -> &dyn Any> { - self - } -} - -/// Represents any kind of translation unit and the generic operations -/// applicable to all the implementors -pub trait TranslationUnit<'a>: AsTranslationUnit<'a> + Any> + Display + Debug { - /// Returns the file, being the addition of the path property plus the file stem plus - /// the extension property - /// - /// # Examples - /// - /// ``` - /// use std::borrow::Cow; - /// use std::path::PathBuf; - /// use zork::bounds::TranslationUnit; - /// use zork::project_model::sourceset::SourceFile; - /// - /// let source_file = SourceFile { - /// path: PathBuf::from("/usr/include"), - /// file_stem: Cow::from("std"), - /// extension: Cow::from("h"), - /// }; - /// - /// assert_eq!(source_file.file(), PathBuf::from("/usr/include/std.h")); - /// - /// let source_file_compat = SourceFile { - /// path: PathBuf::from("/usr/include"), - /// file_stem: Cow::from("std.compat"), - /// extension: Cow::from("h"), - /// }; - /// - /// assert_eq!(source_file_compat.file(), PathBuf::from("/usr/include/std.compat.h")); - /// ``` - fn file(&self) -> PathBuf; - - /// Outputs the declared path for `self`, being self the translation unit - fn path(&self) -> &PathBuf; - - /// Outputs the declared file stem for this translation unit - fn file_stem(&self) -> &Cow<'_, str>; - - /// Outputs the declared extension for `self` - fn extension(&self) -> &Cow<'_, str>; - - /// Outputs the file stem concatenated with the extension for a given tu - fn file_with_extension(&self) -> String { - format!("{}.{}", self.file_stem(), self.extension()) - } -} diff --git a/zork++/src/lib/cache/compile_commands.rs b/zork++/src/lib/cache/compile_commands.rs index 0e2a189e..c1b5095d 100644 --- a/zork++/src/lib/cache/compile_commands.rs +++ b/zork++/src/lib/cache/compile_commands.rs @@ -20,8 +20,7 @@ pub(crate) fn map_generated_commands_to_compilation_db( let generated_commands = cache.get_all_commands_iter(); let mut compilation_db_entries: Vec = - Vec::with_capacity(cache.count_total_generated_commands()); - // TODO: compilation_db_entries.push(latest_commands.linker) + Vec::with_capacity(cache.count_total_generated_commands()); // Without the linker one for command in generated_commands { compilation_db_entries.push(CompileCommand::from(command)); @@ -58,15 +57,3 @@ impl From<&SourceCommandLine> for CompileCommand { } } } - -// TODO review how the linker command line must be specified for the compile_commands.json -// impl From<&LinkerCommandLine> for CompileCommands { -// fn from(value: &LinkerCommandLine) -> Self { -// let value = value.clone(); -// Self { -// directory: value.directory, -// file: value.filename, -// arguments: value.args, -// } -// } -// } diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index c192db8f..40b0a9f5 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -14,21 +14,16 @@ use std::{ path::{Path, PathBuf}, }; -use crate::bounds::TranslationUnit; -use crate::cache::compile_commands::CompileCommands; -use crate::compiler::TranslationUnitKind; - +use crate::domain::translation_unit::{TranslationUnit, TranslationUnitKind}; use crate::project_model::sourceset::SourceFile; +use crate::utils::constants::CACHE_FILE_EXT; use crate::{ cli::{ input::CliArgs, - output::commands::{CommandExecutionResult, Commands, SourceCommandLine}, + output::commands::{Commands, SourceCommandLine}, }, project_model::{compiler::CppCompiler, ZorkModel}, - utils::{ - self, - constants::{self, GCC_CACHE_DIR}, - }, + utils::{self, constants::GCC_CACHE_DIR}, }; use serde::{Deserialize, Serialize}; use walkdir::WalkDir; @@ -37,28 +32,33 @@ use walkdir::WalkDir; /// for the target [`CppCompiler`] pub fn load<'a>(program_data: &'a ZorkModel<'_>, cli_args: &CliArgs) -> Result> { let compiler = program_data.compiler.cpp_compiler; - let cache_path = &program_data - .build - .output_dir - .join("zork") - .join("cache") - .join(compiler.as_ref()); - - let cache_file_path = cache_path.join(constants::ZORK_CACHE_FILENAME); - - if !Path::new(&cache_file_path).exists() { - File::create(cache_file_path).with_context(|| "Error creating the cache file")?; + let cache_path = &program_data.build.output_dir.join("zork").join("cache"); + + let cache_file_path = cache_path + .join(compiler.as_ref()) + .with_extension(CACHE_FILE_EXT); + + // TODO: analyze if the clear cache must be performed by target and/or active cfg file(s) + // TODO: should we just have a cache dir with the __.json or similar? + // Or just ...//_.json + let mut cache = if !Path::new(&cache_file_path).exists() { + File::create(&cache_file_path).with_context(|| "Error creating the cache file")?; + helpers::initialize_default_cache(compiler, cache_file_path)? } else if Path::new(cache_path).exists() && cli_args.clear_cache { fs::remove_dir_all(cache_path).with_context(|| "Error cleaning the Zork++ cache")?; fs::create_dir(cache_path) .with_context(|| "Error creating the cache subdirectory for {compiler}")?; - File::create(cache_file_path) + File::create(&cache_file_path) .with_context(|| "Error creating the cache file after cleaning the cache")?; - } - - let mut cache: ZorkCache = utils::fs::load_and_deserialize(&cache_path) - .with_context(|| "Error loading the Zork++ cache")?; - cache.compiler = compiler; + helpers::initialize_default_cache(compiler, cache_file_path)? + } else { + log::trace!( + "Loading Zork++ cache file for {compiler} at: {:?}", + cache_file_path + ); + utils::fs::load_and_deserialize(&cache_file_path) + .with_context(|| "Error loading the Zork++ cache")? + }; cache .run_tasks(program_data) @@ -67,36 +67,21 @@ pub fn load<'a>(program_data: &'a ZorkModel<'_>, cli_args: &CliArgs) -> Result, mut cache: ZorkCache, test_mode: bool) -> Result<()> { - let cache_path = &program_data - .build - .output_dir - .join("zork") - .join("cache") - .join(program_data.compiler.cpp_compiler.as_ref()) - .join(constants::ZORK_CACHE_FILENAME); - - cache.run_final_tasks(program_data, test_mode)?; - cache.last_program_execution = Utc::now(); - - utils::fs::serialize_object_to_file(cache_path, &cache) - .with_context(move || "Error saving data to the Zork++ cache") -} - #[derive(Serialize, Deserialize, Default)] pub struct ZorkCache<'a> { pub compiler: CppCompiler, - pub last_program_execution: DateTime, pub compilers_metadata: CompilersMetadata<'a>, pub generated_commands: Commands, - // pub new_commands: bool //< if the current iteration added some new command with respect the - // previous one + pub metadata: CacheMetadata, } impl<'a> ZorkCache<'a> { - pub fn last_program_execution(&self) -> &DateTime { - &self.last_program_execution + pub fn save(&mut self, program_data: &ZorkModel<'_>) -> Result<()> { + self.run_final_tasks(program_data)?; + self.metadata.last_program_execution = Utc::now(); + + utils::fs::serialize_object_to_file(&self.metadata.cache_file_path, self) + .with_context(move || "Error saving data to the Zork++ cache") } pub fn get_cmd_for_translation_unit_kind>( @@ -104,13 +89,13 @@ impl<'a> ZorkCache<'a> { translation_unit: &T, translation_unit_kind: &TranslationUnitKind, ) -> Option<&mut SourceCommandLine> { - return match translation_unit_kind { + match translation_unit_kind { TranslationUnitKind::ModuleInterface => self.get_module_ifc_cmd(translation_unit), TranslationUnitKind::ModuleImplementation => self.get_module_impl_cmd(translation_unit), TranslationUnitKind::SourceFile => self.get_source_cmd(translation_unit), TranslationUnitKind::ModularStdLib => todo!(), TranslationUnitKind::HeaderFile => todo!(), - }; + } } fn get_module_ifc_cmd>( @@ -120,7 +105,7 @@ impl<'a> ZorkCache<'a> { self.generated_commands .interfaces .iter_mut() - .find(|mi| *module_interface_model.file() == (*mi).path()) + .find(|mi| module_interface_model.path().eq(&mi.path())) } fn get_module_impl_cmd>( @@ -130,7 +115,7 @@ impl<'a> ZorkCache<'a> { self.generated_commands .implementations .iter_mut() - .find(|mi| *module_impl_model.file() == (*mi).path()) + .find(|mi| *module_impl_model.path() == (*mi).path()) } fn get_source_cmd>( @@ -140,7 +125,7 @@ impl<'a> ZorkCache<'a> { self.generated_commands .sources .iter_mut() - .find(|mi| module_impl_model.file() == (*mi).path()) + .find(|mi| module_impl_model.path() == (*mi).path()) } /// The tasks associated with the cache after load it from the file system @@ -162,32 +147,14 @@ impl<'a> ZorkCache<'a> { } /// Runs the tasks just before end the program and save the cache - fn run_final_tasks( - &mut self, - program_data: &ZorkModel<'_>, - // commands: Commands, - test_mode: bool, - ) -> Result<()> { - // if self.save_generated_commands(commands, program_data, test_mode) - // && program_data.project.compilation_db - // { - // compile_commands::map_generated_commands_to_compilation_db(self)?; - // } - // - if let Some(_new_commands) = self.save_generated_commands(program_data, test_mode) { - if program_data.project.compilation_db { - // TODO:: pass the new commands - compile_commands::map_generated_commands_to_compilation_db(self)?; - } + fn run_final_tasks(&mut self, program_data: &ZorkModel<'_>) -> Result<()> { + if program_data.project.compilation_db && self.metadata.regenerate_compilation_database { + compile_commands::map_generated_commands_to_compilation_db(self)?; } - if !(program_data.compiler.cpp_compiler == CppCompiler::MSVC) - && program_data.modules.is_some() - { + if !(program_data.compiler.cpp_compiler == CppCompiler::MSVC) { self.compilers_metadata.system_modules = program_data .modules - .as_ref() - .unwrap() .sys_modules .iter() .map(|e| e.to_string()) @@ -197,75 +164,6 @@ impl<'a> ZorkCache<'a> { Ok(()) } - /// Stores the generated commands for the process in the Cache. - /// ### Return: - /// a [`Option `] indicating whether there's new generated commands (non cached), so - /// the compile commands must be regenerated - fn save_generated_commands( - &mut self, - // commands: Commands, - _model: &ZorkModel, - _test_mode: bool, // TODO: tests linker cmd? - ) -> Option { - log::debug!("Storing in the cache the last generated command lines..."); - // self.compiler = commands.compiler; - // let _process_no = if !self.generated_commands.is_empty() { - // // TODO: do we now need this one? - // // self.generated_commands.last().unwrap().cached_process_num + 1 - // 0 - // } else { - // 1 - // }; - - // Generating the compilation database if enabled, and some file has been added, modified - // or comes from a previous failure status - - // TODO: oh, fk, I get it finally. We should only regenerate the compilation database if - // the generated command line has changed! (which is highly unlikely) - // TODO: Create a wrapper enumerated over the Vec, so that we can store in the - // array full cached process, and have a variant that only points to the initial file - - // TODO missing the one that determines if there's a new compilation database that must be generated - // something like and iter that counts if at least one has been modified ?? - // let at_least_one_changed = commands. - // self.generated_commands = commands; - - self.get_all_commands_iter() // TODO: Review the conditions and ensure that are the ones that we're looking for - .any(|cmd| { - cmd.need_to_build || cmd.execution_result.eq(&CommandExecutionResult::Success) - }); - - // INSTEAD OF THIS, we just can return an Optional with the compilation database, so we can serialize the args in the compile_commands.json - // format and then join them in a one-liner string, so they're easy to read and/or copy - None - } - - /* fn normalize_execution_result_status( - // TODO: pending to re-implement it - // ALe, don't read again this. We just need to fix the implementation when the commands - // are generated, or even better, bring them from the cache - // So maybe, we can start by fix the thing on early stages - // discard cache if the target zork cfg file has been modified would be awesome - // then, we can have all of them paired in a hashmap with a unique autoincremental - // generated, and split by interfaces and so on and so forth, and read the commands - // from the cache - &self, - module_command_line: &SourceCommandLine, - ) -> CommandExecutionResult { - if module_command_line - .execution_result - .eq(&CommandExecutionResult::Unprocessed) - { - if let Some(prev_entry) = self.is_file_cached(module_command_line.path()) { - prev_entry.execution_result - } else { - module_command_line.execution_result - } - } else { - module_command_line.execution_result - } - } */ - /// Method that returns the HashMap that holds the environmental variables that must be passed /// to the underlying shell pub fn get_process_env_args(&self) -> &EnvVars { @@ -331,9 +229,7 @@ impl<'a> ZorkCache<'a> { { program_data // TODO: review this, since it's too late and I am just satisfying the borrow checker .modules - .as_ref() - .map(|modules| modules.sys_modules.clone()) - .unwrap_or_default() + .sys_modules .iter() .any(|sys_mod| { file.file_name() @@ -358,41 +254,12 @@ impl<'a> ZorkCache<'a> { } #[derive(Deserialize, Serialize, Debug, Default, Clone)] -pub struct CommandsDetails { - cached_process_num: i32, - generated_at: DateTime, - interfaces: Vec, - implementations: Vec, - sources: Vec, - pre_tasks: Vec, - main: MainCommandLineDetail, -} - -#[derive(Deserialize, Serialize, Debug, Default, Clone)] -pub struct CommandDetail { - directory: String, - file: String, - command_line: String, - execution_result: CommandExecutionResult, -} - -impl CommandDetail { - #[inline(always)] - pub fn file_path(&self) -> PathBuf { - Path::new(&self.directory).join(&self.file) - } - - #[inline] - pub fn execution_result(&self) -> CommandExecutionResult { - self.execution_result - } -} - -#[derive(Deserialize, Serialize, Debug, Default, Clone)] -pub struct MainCommandLineDetail { - files: Vec, - execution_result: CommandExecutionResult, - command: String, +pub struct CacheMetadata { + pub process_no: i32, + pub last_program_execution: DateTime, + pub cache_file_path: PathBuf, + #[serde(skip)] + pub regenerate_compilation_database: bool, } /// Type alias for the underlying key-value based collection of environmental variables @@ -551,14 +418,30 @@ mod msvc { } mod helpers { + use super::*; use crate::project_model::ZorkModel; - use std::borrow::Cow; + use std::path::PathBuf; + // TODO: this can be used also on the compiler/mod.rs pub(crate) fn user_declared_system_headers_to_build(program_data: &ZorkModel<'_>) -> bool { - program_data - .modules - .as_ref() - .map(|mods| mods.sys_modules.as_ref()) - .is_some_and(|sys_modules: &Vec>| !sys_modules.is_empty()) + !program_data.modules.sys_modules.is_empty() + } + + pub(crate) fn initialize_default_cache<'a>( + compiler: CppCompiler, + cache_file_path: PathBuf, + ) -> Result> { + let default_initialized = ZorkCache { + compiler, + metadata: CacheMetadata { + cache_file_path: cache_file_path.clone(), + ..Default::default() + }, + ..Default::default() + }; + + utils::fs::serialize_object_to_file(&cache_file_path, &default_initialized) + .with_context(move || "Error saving data to the Zork++ cache")?; + Ok(default_initialized) } } diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index e7ae684e..451dd7f9 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -10,20 +10,17 @@ use serde::{Deserialize, Serialize}; use crate::project_model::compiler::LanguageLevel; -pub trait CommandLineArgument: std::fmt::Display {} -pub trait CommandLineArguments: std::fmt::Display {} - /// Wrapper type for represent and storing a command line argument #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Argument(String); -impl CommandLineArgument for Argument {} -impl CommandLineArgument for &Argument {} - impl Argument { pub fn value(&self) -> &String { &self.0 } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } } impl From<&str> for Argument { @@ -32,6 +29,12 @@ impl From<&str> for Argument { } } +impl From<&String> for Argument { + fn from(value: &String) -> Self { + Self(value.into()) + } +} + impl From> for Argument { fn from(value: Cow<'_, str>) -> Self { Self(value.into()) @@ -102,9 +105,6 @@ impl core::fmt::Display for Argument { #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Arguments(Vec); -impl CommandLineArguments for Arguments {} -impl CommandLineArguments for &Arguments {} - impl core::fmt::Display for Arguments { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.iter().try_for_each(|arg| write!(f, "{} ", arg)) @@ -227,9 +227,9 @@ pub mod clang_args { // The Windows variant is a Zork++ feature to allow the users to write `import std;` // under -std=c++20 with clang linking against GCC with // some MinGW installation or similar - pub(crate) fn implicit_module_map(out_dir: &Path) -> Argument { + pub(crate) fn implicit_module_map<'a>(out_dir: &Path) -> Cow<'a, str> { if std::env::consts::OS.eq("windows") { - Argument::from(format!( + Cow::Owned(format!( "-fmodule-map-file={}", out_dir .join("zork") @@ -238,19 +238,19 @@ pub mod clang_args { .display() )) } else { - Argument::from("-fimplicit-module-maps") + Cow::Borrowed("-fimplicit-module-maps") } } - pub(crate) fn add_prebuilt_module_path(compiler: CppCompiler, out_dir: &Path) -> Argument { - Argument::from(format!( + pub(crate) fn add_prebuilt_module_path(compiler: CppCompiler, out_dir: &Path) -> String { + format!( "-fprebuilt-module-path={}", out_dir .join(compiler.as_ref()) .join("modules") .join("interfaces") .display() - )) + ) } pub(crate) fn add_direct_module_interfaces_dependencies( @@ -275,17 +275,15 @@ pub mod clang_args { } pub mod msvc_args { + use crate::domain::translation_unit::TranslationUnit; use crate::{ - bounds::TranslationUnit, - cache::ZorkCache, - cli::output::commands::SourceCommandLine, - project_model::{compiler::StdLibMode, ZorkModel}, + cache::ZorkCache, cli::output::commands::SourceCommandLine, + project_model::compiler::StdLibMode, }; use super::Arguments; pub(crate) fn generate_std_cmd( - _model: &ZorkModel<'_>, // TODO: ensure and then remove cache: &ZorkCache, stdlib_mode: StdLibMode, ) -> SourceCommandLine { @@ -314,7 +312,7 @@ pub mod msvc_args { }); arguments.create_and_push("/c"); - arguments.create_and_push(stdlib_sf.file()); + arguments.create_and_push(stdlib_sf.path()); arguments.create_and_push("/ifcOutput"); arguments.create_and_push(format! { "{}", stdlib_bmi_path.display() diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index 72b262a0..c57e54a3 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -11,10 +11,11 @@ use std::{ }; use super::arguments::Argument; -use crate::bounds::TranslationUnit; use crate::cache::EnvVars; use crate::cli::output::arguments::Arguments; +use crate::cli::output::commands::helpers::load_common_data; use crate::compiler::data_factory::{CommonArgs, CompilerCommonArguments}; +use crate::domain::translation_unit::TranslationUnit; use crate::{ cache::ZorkCache, project_model::{compiler::CppCompiler, ZorkModel}, @@ -29,27 +30,22 @@ use serde::{Deserialize, Serialize}; pub fn run_generated_commands( program_data: &ZorkModel<'_>, cache: &mut ZorkCache<'_>, -) -> Result { +) -> Result<()> { log::info!("Proceeding to execute the generated commands..."); - let general_args = &cache.generated_commands.general_args.get_args(); - let compiler_specific_shared_args = &cache.generated_commands.compiler_common_args.get_args(); - - let env_args = cache.get_process_env_args().clone(); // TODO: this is yet better than clone the - // generated commands (maybe) but I'm not - // happy with it + let (general_args, compiler_specific_shared_args, env_vars) = load_common_data(cache)?; for sys_module in &cache.generated_commands.system_modules { // TODO: will be deprecated soon, hopefully // But while isn't deleted, we could normalize them into SourceCommandLine // And then, consider to join them into the all generated commands iter - execute_command(program_data, sys_module.1, &env_args)?; + execute_command(program_data, sys_module.1, &env_vars)?; } let translation_units = cache .generated_commands .get_all_command_lines() - .filter(|scl| scl.need_to_build) + .filter(|scl| scl.status.eq(&TranslationUnitStatus::PendingToBuild)) .collect::>(); let compile_but_dont_link: [Argument; 1] = @@ -67,10 +63,11 @@ pub fn run_generated_commands( .chain(translation_unit_cmd.args.iter()) .collect(); - let r = execute_command(program_data, &translation_unit_cmd_args, &env_args); - translation_unit_cmd.execution_result = CommandExecutionResult::from(&r); + let r = execute_command(program_data, &translation_unit_cmd_args, &env_vars); + translation_unit_cmd.status = TranslationUnitStatus::from(&r); if let Err(e) = r { + cache.save(program_data)?; return Err(e); } else if !r.as_ref().unwrap().success() { let err = eyre!( @@ -81,32 +78,35 @@ pub fn run_generated_commands( } } - if !cache.generated_commands.linker.args.is_empty() { - log::debug!("Processing the linker command line..."); - - let r = execute_command( - program_data, - &general_args - .iter() - .chain(compiler_specific_shared_args.iter()) - .chain(cache.generated_commands.linker.args.iter()) - .collect::(), - &env_args, - ); + log::debug!("Processing the linker command line..."); + let r = execute_command( + program_data, + &general_args + .iter() + .chain(compiler_specific_shared_args.iter()) + .chain( + cache + .generated_commands + .linker + .get_target_output_for(program_data.compiler.cpp_compiler) + .iter(), + ) + .chain(cache.generated_commands.linker.byproducts.iter()) + .collect::(), + &env_vars, + ); - cache.generated_commands.linker.execution_result = CommandExecutionResult::from(&r); + cache.generated_commands.linker.execution_result = TranslationUnitStatus::from(&r); - if let Err(e) = r { - return Err(e); - } else if !r.as_ref().unwrap().success() { - return Err(eyre!( - "Ending the program, because the linker command line execution failed", - )); - } + if let Err(e) = r { + return Err(e); + } else if !r.as_ref().unwrap().success() { + return Err(eyre!( + "Ending the program, because the linker command line execution failed", + )); } - Ok(CommandExecutionResult::Success) // TODO: consider a new variant, like AllSuccedeed - // or better, change the return for something better + Ok(()) } /// Executes a new [`std::process::Command`] to run the generated binary @@ -115,7 +115,7 @@ pub fn autorun_generated_binary( compiler: &CppCompiler, output_dir: &Path, executable_name: &str, -) -> Result { +) -> Result<()> { let args = &[Argument::from( output_dir .join(compiler.as_ref()) @@ -128,14 +128,14 @@ pub fn autorun_generated_binary( args.join(" ") ); - Ok(CommandExecutionResult::from( - std::process::Command::new(Argument::from( - output_dir.join(compiler.as_ref()).join(executable_name), - )) - .spawn()? - .wait() - .with_context(|| format!("[{compiler}] - Command {:?} failed!", args.join(" "))), + std::process::Command::new(Argument::from( + output_dir.join(compiler.as_ref()).join(executable_name), )) + .spawn()? + .wait() + .with_context(|| format!("[{compiler}] - Command {:?} failed!", args.join(" ")))?; + + Ok(()) } /// Executes a new [`std::process::Command`] configured according the chosen @@ -173,23 +173,17 @@ pub struct SourceCommandLine { pub directory: PathBuf, pub filename: String, pub args: Arguments, - pub need_to_build: bool, - pub execution_result: CommandExecutionResult, - // TODO an enum with the Kind OF TU that is generating this scl? + pub status: TranslationUnitStatus, } impl SourceCommandLine { - pub fn new<'a, T: TranslationUnit<'a>>( - // TODO init it as a args holder, but doesn't have the status yet - tu: &T, - args: Arguments, - ) -> Self { + // TODO T instead of &T? + pub fn new<'a, T: TranslationUnit<'a>>(tu: &T, args: Arguments) -> Self { Self { - directory: tu.path().to_path_buf(), - filename: tu.file_with_extension(), + directory: PathBuf::from(tu.parent()), + filename: tu.filename(), args, - need_to_build: true, - execution_result: CommandExecutionResult::Unprocessed, + status: TranslationUnitStatus::PendingToBuild, } } @@ -200,28 +194,37 @@ impl SourceCommandLine { #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct LinkerCommandLine { - pub args: Arguments, - pub execution_result: CommandExecutionResult, + pub target: Argument, + pub byproducts: Arguments, + pub extra_args: Arguments, + pub execution_result: TranslationUnitStatus, } impl LinkerCommandLine { + pub fn get_target_output_for(&self, compiler: CppCompiler) -> Vec { + match compiler { + CppCompiler::CLANG | CppCompiler::GCC => { + vec![Argument::from("-o"), self.target.clone()] + } + CppCompiler::MSVC => vec![self.target.clone()], + } + } + /// Saves the path at which a compilation product of any translation unit will be placed, /// in order to add it to the files that will be linked to generate the final product /// in the two-phase compilation model pub fn add_buildable_at(&mut self, path: &Path) { - self.args.push(Argument::from(path)); + self.byproducts.push(Argument::from(path)); } - // TODO: just maybe a Cow for everyone? - /// Owned version of TODO link pub fn add_owned_buildable_at(&mut self, path: PathBuf) { - self.args.push(path.into()); + self.byproducts.push(path.into()); } } /// Holds the generated command line arguments for a concrete compiler -#[derive(Serialize, Deserialize, Default)] +#[derive(Serialize, Deserialize, Default, Debug)] pub struct Commands { pub compiler: CppCompiler, // TODO: review if we can afford this field given the new // architechture @@ -229,8 +232,8 @@ pub struct Commands { pub c_compat_stdlib: Option, pub system_modules: HashMap, - pub general_args: CommonArgs, - pub compiler_common_args: Box, + pub general_args: Option, + pub compiler_common_args: Option>, pub interfaces: Vec, pub implementations: Vec, @@ -239,7 +242,7 @@ pub struct Commands { } impl Commands { - /// Returns an [std::iter::Chain] (behind the opaque impl clause return type signature) + /// Returns a [std::iter::Chain] (behind the opaque impl clause return type signature) /// which points to all the generated commmands for the two variants of the compilers vendors C++ modular /// standard libraries implementations (see: [crate::project_model::compiler::StdLibMode]) /// joined to all the commands generated for every [TranslationUnit] declared by the user for @@ -251,6 +254,7 @@ impl Commands { .as_mut_slice() .iter_mut() .chain(self.c_compat_stdlib.as_mut_slice().iter_mut()) + // TODO: chain pre-tasks (system headers) .chain(self.interfaces.as_mut_slice().iter_mut()) .chain(self.implementations.as_mut_slice().iter_mut()) .chain(self.sources.as_mut_slice().iter_mut()) @@ -291,10 +295,10 @@ fn collect_source_command_line( }) } -/// Holds a custom representation of the execution of -/// a command line in a shell. +/// The different states of a translation unit in the whole lifecycle of +/// the build process and across different iterations of the same #[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq)] -pub enum CommandExecutionResult { +pub enum TranslationUnitStatus { /// A command that is executed correctly Success, /// A skipped command due to previous successful iterations @@ -302,36 +306,70 @@ pub enum CommandExecutionResult { /// A command which is return code indicates an unsuccessful execution Failed, /// Whenever a translation unit must be rebuilt + #[default] PendingToBuild, + /// The associated [`TranslationUnit`] has been deleted from the user's configuration and therefore, + /// it should be removed from the cache as well as its generated byproducts + ToDelete, /// The execution failed, returning a [`Result`] with the Err variant Error, - /// A previous state before executing a command line - #[default] - Unprocessed, } -impl From> for CommandExecutionResult { +impl From> for TranslationUnitStatus { fn from(value: Result) -> Self { - handle_command_execution_result(&value) + helpers::handle_command_execution_result(&value) } } -impl From<&Result> for CommandExecutionResult { +impl From<&Result> for TranslationUnitStatus { fn from(value: &Result) -> Self { - handle_command_execution_result(value) + helpers::handle_command_execution_result(value) } } -/// Convenient way of handle a command execution result avoiding duplicate code -fn handle_command_execution_result(value: &Result) -> CommandExecutionResult { - match value { - Ok(r) => { - if r.success() { - CommandExecutionResult::Success - } else { - CommandExecutionResult::Failed +mod helpers { + use crate::cache::{EnvVars, ZorkCache}; + use crate::cli::output::arguments::Arguments; + use crate::cli::output::commands::TranslationUnitStatus; + use crate::utils::constants::error_messages; + use color_eyre::eyre::{ContextCompat, Result}; + use std::process::ExitStatus; + + /// Convenient way of handle a command execution result avoiding duplicate code + pub(crate) fn handle_command_execution_result( + value: &Result, + ) -> TranslationUnitStatus { + match value { + Ok(r) => { + if r.success() { + TranslationUnitStatus::Success + } else { + TranslationUnitStatus::Failed + } } + Err(_) => TranslationUnitStatus::Error, } - Err(_) => CommandExecutionResult::Error, + } + + pub(crate) fn load_common_data( + cache: &mut ZorkCache<'_>, + ) -> Result<(Arguments, Arguments, EnvVars)> { + let general_args = cache + .generated_commands + .general_args + .as_ref() + .expect(error_messages::GENERAL_ARGS_NOT_FOUND) + .get_args(); + + let compiler_specific_shared_args = cache + .generated_commands + .compiler_common_args + .as_ref() + .with_context(|| error_messages::COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND)? + .get_args(); + + let env_vars = cache.get_process_env_args().clone(); + + Ok((general_args, compiler_specific_shared_args, env_vars)) } } diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index 19d7bf05..09651740 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -7,9 +7,9 @@ use std::{borrow::Cow, path::Path, rc::Rc}; use serde::{Deserialize, Serialize}; use crate::{ - bounds::ExtraArgs, cache::ZorkCache, cli::output::arguments::{clang_args, Argument, Arguments}, + domain::target::ExtraArgs, project_model::compiler::{CppCompiler, StdLib}, project_model::ZorkModel, }; @@ -76,7 +76,7 @@ pub fn compiler_common_arguments_factory( /// Allows to have a common interface for any type that represents a data structure which its /// purpose is to hold common [`Argument`] across the diferent kind of [`TranslationUnit`] #[typetag::serde(tag = "type")] -pub trait CompilerCommonArguments { +pub trait CompilerCommonArguments: std::fmt::Debug { fn get_args(&self) -> Arguments; } impl Default for Box { @@ -96,11 +96,12 @@ impl CompilerCommonArguments for ClangCommonArgs { let mut args = Arguments::default(); args.push(self.std_lib.as_arg()); args.create_and_push(&self.implicit_modules); - args.push(self.implicit_module_map.clone()); + args.create_and_push(&self.implicit_module_map); args.create_and_push(&self.prebuilt_module_path); args } } + #[typetag::serde] impl CompilerCommonArguments for MsvcCommonArgs { fn get_args(&self) -> Arguments { @@ -121,6 +122,7 @@ impl CompilerCommonArguments for MsvcCommonArgs { args } } + #[typetag::serde] impl CompilerCommonArguments for GccCommonArgs { fn get_args(&self) -> Arguments { @@ -134,21 +136,19 @@ impl CompilerCommonArguments for GccCommonArgs { pub struct ClangCommonArgs { std_lib: StdLib, implicit_modules: Cow<'static, str>, - implicit_module_map: Argument, - prebuilt_module_path: Cow<'static, str>, + implicit_module_map: Cow<'static, str>, + prebuilt_module_path: String, } impl ClangCommonArgs { pub fn new(model: &ZorkModel<'_>) -> Self { + let compiler = model.compiler.cpp_compiler; let out_dir: &Path = model.build.output_dir.as_ref(); Self { std_lib: model.compiler.std_lib.unwrap_or_default(), implicit_modules: "-fimplicit-modules".into(), implicit_module_map: clang_args::implicit_module_map(out_dir), - prebuilt_module_path: Cow::Owned(format!( - "-fprebuilt-module-path={}/clang/modules/interfaces", - out_dir.display() - )), + prebuilt_module_path: clang_args::add_prebuilt_module_path(compiler, out_dir), } } } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index ba50a843..752bfcbc 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -8,21 +8,27 @@ pub mod data_factory; use color_eyre::Result; use std::path::Path; -use crate::bounds::{ExecutableTarget, TranslationUnit}; -use crate::cli::input::{CliArgs, Command}; -use crate::cli::output::arguments::{msvc_args, Arguments}; -use crate::cli::output::commands::{CommandExecutionResult, SourceCommandLine}; -use crate::project_model::compiler::StdLibMode; -use crate::project_model::sourceset::SourceFile; -use crate::utils::constants; +use crate::cli::output::commands::TranslationUnitStatus; use crate::{ cache::ZorkCache, - cli::output::arguments::Argument, + cli::{ + input::{CliArgs, Command}, + output::{ + arguments::{msvc_args, Argument, Arguments}, + commands::SourceCommandLine, + }, + }, + domain::{ + target::ExecutableTarget, + translation_unit::{TranslationUnit, TranslationUnitKind}, + }, project_model::{ - compiler::CppCompiler, + compiler::{CppCompiler, StdLibMode}, modules::{ModuleImplementationModel, ModuleInterfaceModel}, + sourceset::SourceFile, ZorkModel, }, + utils::constants, }; use self::data_factory::CommonArgs; @@ -34,108 +40,72 @@ pub fn generate_commands<'a>( mut cache: ZorkCache<'a>, cli_args: &'a CliArgs, ) -> Result> { - // TODO: guard it with a presence check */ - // They should only be generated the first time or on every cache reset - cache.generated_commands.general_args = CommonArgs::from(model); - cache.generated_commands.compiler_common_args = - data_factory::compiler_common_arguments_factory(model, &cache); + // Load the general args and the compiler specific ones if it's necessary + load_flyweights_for_general_shared_data(model, &mut cache); - // TODO: add them to the commands DS, so they are together until they're generated // Build the std library as a module - generate_modular_stdlibs_cmds(model, &mut cache); // TODO: ward it with an if for only call this fn for the + generate_modular_stdlibs_cmds(model, &mut cache); - // 1st - Build the modules - if let Some(modules) = &model.modules { - // TODO: re-think again this optional - // Pre-tasks - if model.compiler.cpp_compiler == CppCompiler::GCC && !modules.sys_modules.is_empty() { - helpers::build_sys_modules(model, &mut cache) - } + // Pre-tasks + if model.compiler.cpp_compiler != CppCompiler::MSVC && !model.modules.sys_modules.is_empty() { + helpers::build_sys_modules(model, &mut cache) + } - process_modules(model, &mut cache, cli_args)?; - }; + // Translation units and linker + // 1st - Build the modules + process_modules(model, &mut cache, cli_args)?; // 2nd - Generate the commands for the non-module sources generate_sources_cmds_args(model, &mut cache, cli_args)?; - // 3rd - Genates the commands for the 'targets' declared by the user - generate_linkage_targets_commands(model, &mut cache, cli_args)?; + // 3rd - Generate the linker command for the 'target' declared by the user + generate_linkage_targets_commands(model, &mut cache, cli_args); Ok(cache) } -/// Generates the cmds for build the C++ standard libraries (std and std.compat) acording to the specification +/// Adds to the cache the data on the *flyweight* data structures that holds all the +/// command line arguments that are shared among the command lines +fn load_flyweights_for_general_shared_data(model: &ZorkModel, cache: &mut ZorkCache) { + if cache.generated_commands.general_args.is_none() { + cache.generated_commands.general_args = Some(CommonArgs::from(model)); + } + + if cache.generated_commands.compiler_common_args.is_none() { + cache.generated_commands.compiler_common_args = Some( + data_factory::compiler_common_arguments_factory(model, cache), + ); + } +} + +/// Generates the cmds for build the C++ standard libraries (std and std.compat) according to the specification /// of each compiler vendor fn generate_modular_stdlibs_cmds(model: &ZorkModel<'_>, cache: &mut ZorkCache) { let compiler = model.compiler.cpp_compiler; - let lpe = cache.last_program_execution; + let lpe = cache.metadata.last_program_execution; // TODO: remaining ones: Clang, GCC if compiler.eq(&CppCompiler::MSVC) { - let vs_stdlib_path = cache - .compilers_metadata - .msvc - .vs_stdlib_path - .as_ref() - .unwrap() - .path(); if let Some(cpp_stdlib_cmd) = cache.generated_commands.cpp_stdlib.as_mut() { - let build_std = helpers::translation_unit_must_be_built( - compiler, - &lpe, - cpp_stdlib_cmd, - vs_stdlib_path, - ); - - cpp_stdlib_cmd.need_to_build = build_std; - if !build_std - && !cpp_stdlib_cmd - .execution_result - .eq(&CommandExecutionResult::Cached) - { - cpp_stdlib_cmd.execution_result = CommandExecutionResult::Cached; - } else { - cpp_stdlib_cmd.execution_result = CommandExecutionResult::PendingToBuild; - } + cpp_stdlib_cmd.status = + helpers::determine_translation_unit_status(compiler, &lpe, cpp_stdlib_cmd); } else { log::info!( "Generating the command for build the {:?} C++ standard library implementation", compiler ); - let cpp_stdlib = msvc_args::generate_std_cmd(model, cache, StdLibMode::Cpp); - cache.generated_commands.cpp_stdlib = Some(cpp_stdlib); + let cpp_stdlib_cmd = msvc_args::generate_std_cmd(cache, StdLibMode::Cpp); + cache.generated_commands.cpp_stdlib = Some(cpp_stdlib_cmd); } - let vs_ccompat_stdlib_path = cache - .compilers_metadata - .msvc - .vs_c_stdlib_path - .as_ref() - .unwrap() - .path(); if let Some(ccompat_stdlib_cmd) = cache.generated_commands.c_compat_stdlib.as_mut() { - let build_ccompat = helpers::translation_unit_must_be_built( - compiler, - &lpe, - ccompat_stdlib_cmd, - vs_ccompat_stdlib_path, - ); - - ccompat_stdlib_cmd.need_to_build = build_ccompat; - if !build_ccompat - && !ccompat_stdlib_cmd - .execution_result - .eq(&CommandExecutionResult::Cached) - { - ccompat_stdlib_cmd.execution_result = CommandExecutionResult::Cached; - } else { - ccompat_stdlib_cmd.execution_result = CommandExecutionResult::PendingToBuild; - } + ccompat_stdlib_cmd.status = + helpers::determine_translation_unit_status(compiler, &lpe, ccompat_stdlib_cmd); } else { log::info!( "Generating the command for build the {:?} C++ standard library implementation", compiler ); - let ccompat_stdlib = msvc_args::generate_std_cmd(model, cache, StdLibMode::CCompat); + let ccompat_stdlib = msvc_args::generate_std_cmd(cache, StdLibMode::CCompat); cache.generated_commands.c_compat_stdlib = Some(ccompat_stdlib); } } @@ -146,7 +116,7 @@ fn process_modules<'a>( cache: &mut ZorkCache<'a>, cli_args: &'a CliArgs, ) -> Result<()> { - let modules = model.modules.as_ref().unwrap(); // TODO: review this opt again + let modules = &model.modules; log::info!("Generating the commands for the module interfaces and partitions..."); process_kind_translation_units( @@ -169,27 +139,6 @@ fn process_modules<'a>( Ok(()) } -/// Generates the command line that will be passed to the linker to generate an [`ExecutableTarget`] -/// Generates the commands for the C++ modules declared in the project -/// -/// Legacy: -/// If this flow is enabled by the Cli arg `Tests`, then the executable will be generated -/// for the files and properties declared for the tests section in the configuration file -fn generate_linkage_targets_commands<'a>( - model: &'a ZorkModel<'_>, - cache: &'a mut ZorkCache<'_>, - cli_args: &'a CliArgs, -) -> Result<()> { - // TODO: Shouldn't we start to think about named targets? So introduce the static and dynamic - // libraries wouldn't be such a pain? - let is_tests_run = cli_args.command.eq(&Command::Test); - if is_tests_run { - generate_linker_general_command_line_args(model, cache, &model.tests) - } else { - generate_linker_general_command_line_args(model, cache, &model.executable) - } -} - fn generate_sources_cmds_args<'a>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, @@ -216,14 +165,68 @@ fn generate_sources_cmds_args<'a>( Ok(()) } -pub enum TranslationUnitKind { - ModuleInterface, - ModuleImplementation, - SourceFile, - HeaderFile, - ModularStdLib, +/// Generates the command line that will be passed to the linker to generate an [`ExecutableTarget`] +/// Generates the commands for the C++ modules declared in the project +/// +/// Legacy: +/// If this flow is enabled by the Cli arg `Tests`, then the executable will be generated +/// for the files and properties declared for the tests section in the configuration file +fn generate_linkage_targets_commands<'a>( + model: &'a ZorkModel<'_>, + cache: &'a mut ZorkCache<'_>, + cli_args: &'a CliArgs, +) { + // TODO: Shouldn't we start to think about named targets? So introduce the static and dynamic + // libraries wouldn't be such a pain? + let is_tests_run = cli_args.command.eq(&Command::Test); + if is_tests_run { + generate_linker_general_command_line_args(model, cache, &model.tests); + } else { + generate_linker_general_command_line_args(model, cache, &model.executable); + } +} + +/// Generates the general command line arguments for the desired target +/// +/// **implementation note:** All the final byproducts of the compiled translation units, the object +/// files paths, are added in place to the linker args member when they're created, so we can avoid +/// to clone them everytime we create a new [`SourceCommandLine`] for a given translation unit +pub fn generate_linker_general_command_line_args<'a>( + model: &ZorkModel<'_>, + cache: &mut ZorkCache<'_>, + target: &'a impl ExecutableTarget<'a>, +) { + log::info!("Generating the linker command line..."); + + let linker = &mut cache.generated_commands.linker; + + let compiler = &model.compiler.cpp_compiler; + let out_dir: &Path = model.build.output_dir.as_ref(); + let executable_name = target.name(); + + let target_output = Argument::from( + out_dir + .join(compiler.as_ref()) + .join(executable_name) + .with_extension(constants::BINARY_EXTENSION), + ); + + if linker.target.ne(&target_output) { + match compiler { + CppCompiler::CLANG | CppCompiler::GCC => linker.target = target_output, + CppCompiler::MSVC => linker.target = Argument::from(format!("/Fe{}", target_output)), + }; + } + + if Iterator::ne(linker.extra_args.iter(), target.extra_args().iter()) { + linker.extra_args.clear(); + linker.extra_args.extend_from_slice(target.extra_args()); + } } +/// The core procedure of the commands generation process. +/// +/// It takes care fn process_kind_translation_units<'a, T: TranslationUnit<'a>>( model: &ZorkModel<'_>, cache: &mut ZorkCache<'a>, @@ -232,18 +235,17 @@ fn process_kind_translation_units<'a, T: TranslationUnit<'a>>( for_kind: TranslationUnitKind, ) { let compiler = cache.compiler; - let lpe = cache.last_program_execution; + let lpe = cache.metadata.last_program_execution; translation_units.iter().for_each(|translation_unit| { if let Some(generated_cmd) = cache.get_cmd_for_translation_unit_kind(translation_unit, &for_kind) { - let translation_unit_must_be_rebuilt = helpers::translation_unit_must_be_built(compiler, &lpe, generated_cmd, &translation_unit.file()); + let build_translation_unit = helpers::determine_translation_unit_status(compiler, &lpe, generated_cmd); - if !translation_unit_must_be_rebuilt { - log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &translation_unit.file()); - // TODO: here is where we should use the normalize_execution_result_status - // function, to mark executions as cached (when tu musn't be rebuilt) + if build_translation_unit.ne(&TranslationUnitStatus::PendingToBuild) { + log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &translation_unit.path()); } - generated_cmd.need_to_build = translation_unit_must_be_rebuilt; + + generated_cmd.status = build_translation_unit; } else { let tu_with_erased_type = translation_unit.as_any(); match for_kind { @@ -262,88 +264,18 @@ fn process_kind_translation_units<'a, T: TranslationUnit<'a>>( }); } -/// Generates the general command line arguments for the desired target -/// -/// **implementation note:** All the final byproducts of the compiled translation units, the object -/// files paths, are added in place to the linker args member when they're created, so we can avoid -/// to clone them everytime we create a new [`SourceCommandLine`] for a given translation unit -pub fn generate_linker_general_command_line_args<'a>( - model: &ZorkModel<'_>, - cache: &mut ZorkCache<'_>, - target: &'a impl ExecutableTarget<'a>, -) -> Result<()> { - log::info!("Generating the linker command line..."); - - // NOTE: this early guard is provisional while we don't implement the feature of really having - // named targets for each user declared one (instead of the actual run/tests kind of thing) - if cache.generated_commands.linker.args.is_empty() { - return Ok(()); - } - - let compiler = &model.compiler.cpp_compiler; - let out_dir: &Path = model.build.output_dir.as_ref(); - let executable_name = target.name(); - - let mut arguments = Arguments::default(); - arguments.extend_from_slice(target.extra_args()); - - match compiler { - CppCompiler::CLANG => { - arguments.create_and_push("-o"); - arguments.create_and_push(format!( - "{}", - out_dir - .join(compiler.as_ref()) - .join(executable_name) - .with_extension(constants::BINARY_EXTENSION) - .display() - )); - } - CppCompiler::MSVC => { - arguments.create_and_push(format!( - "/Fo{}\\", - out_dir.join(compiler.as_ref()).display() - )); - arguments.create_and_push(format!( - "/Fe{}", - out_dir - .join(compiler.as_ref()) - .join(executable_name) - .with_extension(constants::BINARY_EXTENSION) - .display() - )); - } - CppCompiler::GCC => { - arguments.create_and_push("-o"); - arguments.create_and_push(format!( - "{}", - out_dir - .join(compiler.as_ref()) - .join(executable_name) - .with_extension(constants::BINARY_EXTENSION) - .display() - )); - } - }; - - arguments.extend(&cache.generated_commands.linker.args); - cache.generated_commands.linker.args = arguments; - - Ok(()) -} - /// Specific operations over source files mod sources { use std::path::Path; use super::helpers; - use crate::bounds::ExtraArgs; use crate::cache::ZorkCache; use crate::cli::output::arguments::Arguments; + use crate::domain::target::{ExecutableTarget, ExtraArgs}; + use crate::domain::translation_unit::TranslationUnit; use crate::project_model::modules::ModuleImplementationModel; use crate::project_model::sourceset::SourceFile; use crate::{ - bounds::{ExecutableTarget, TranslationUnit}, cli::output::{arguments::clang_args, commands::SourceCommandLine}, project_model::{compiler::CppCompiler, modules::ModuleInterfaceModel, ZorkModel}, }; @@ -359,56 +291,17 @@ mod sources { let out_dir = model.build.output_dir.as_ref(); let mut arguments = Arguments::default(); - arguments.push(model.compiler.language_level_arg()); - arguments.create_and_push(if compiler.eq(&CppCompiler::MSVC) { - "/c" - } else { - "-c" - }); arguments.extend_from_slice(model.compiler.extra_args()); arguments.extend_from_slice(target.extra_args()); - match compiler { - CppCompiler::CLANG => { - arguments.push_opt(model.compiler.stdlib_arg()); - arguments.create_and_push("-fimplicit-modules"); - arguments.push(clang_args::implicit_module_map(out_dir)); - arguments.push(clang_args::add_prebuilt_module_path(compiler, out_dir)); - arguments.create_and_push("-o"); - } - CppCompiler::MSVC => { - arguments.create_and_push("/EHsc"); - arguments.create_and_push("/nologo"); - arguments.create_and_push("/reference"); - arguments.create_and_push(format! { - "std={}", cache.compilers_metadata.msvc.stdlib_bmi_path.display() - }); - arguments.create_and_push("/reference"); - arguments.create_and_push(format! { - "std.compat={}", cache.compilers_metadata.msvc.c_stdlib_bmi_path.display() - }); - arguments.create_and_push("/ifcSearchDir"); - arguments.create_and_push( - out_dir - .join(compiler.as_ref()) - .join("modules") - .join("interfaces"), - ); - } - CppCompiler::GCC => { - arguments.create_and_push("-fmodules-ts"); - arguments.create_and_push("-o"); - } - }; - - let obj_file = helpers::generate_obj_file_path(compiler, out_dir, source); + let obj_file = helpers::generate_obj_file(compiler, out_dir, source); let fo = if compiler.eq(&CppCompiler::MSVC) { "/Fo" } else { - "" + "-o" }; arguments.create_and_push(format!("{fo}{}", obj_file.display())); - arguments.create_and_push(source.file()); + arguments.create_and_push(source.path()); let command_line = SourceCommandLine::new(source, arguments); cache.generated_commands.sources.push(command_line); @@ -449,14 +342,14 @@ mod sources { .generated_commands .add_linker_file_path_owned(obj_file); // The input file - arguments.create_and_push(interface.file()); + arguments.create_and_push(interface.path()); } CppCompiler::MSVC => { + arguments.create_and_push("/ifcOutput"); let implicit_lookup_mius_path = out_dir .join(compiler.as_ref()) .join("modules") - .join("interfaces"); - arguments.create_and_push("/ifcOutput"); + .join("interfaces"); // TODO: as shared arg for kind interfaces? arguments.create_and_push(implicit_lookup_mius_path); // The output .obj file @@ -477,15 +370,13 @@ mod sources { } arguments.create_and_push("/TP"); // The input file - arguments.create_and_push(interface.file()) + arguments.create_and_push(interface.path()) } CppCompiler::GCC => { - arguments.create_and_push("-fmodules-ts"); - arguments.create_and_push("-c"); arguments.create_and_push("-x"); arguments.create_and_push("c++"); // The input file - arguments.create_and_push(interface.file()); + arguments.create_and_push(interface.path()); // The output file arguments.create_and_push("-o"); let obj_file = helpers::generate_prebuilt_miu(compiler, out_dir, interface); @@ -514,19 +405,9 @@ mod sources { match compiler { CppCompiler::CLANG => { - arguments.push_opt(model.compiler.stdlib_arg()); - arguments.create_and_push("-fimplicit-modules"); - arguments.create_and_push("-c"); - arguments.push(clang_args::implicit_module_map(out_dir)); - // The resultant object file arguments.create_and_push("-o"); - let obj_file_path = helpers::generate_impl_obj_file( - // TODO review this ones, since they module impl are just raw cpp sources - compiler, - out_dir, - implementation, - ); + let obj_file_path = helpers::generate_obj_file(compiler, out_dir, implementation); cache .generated_commands .add_linker_file_path(&obj_file_path); @@ -540,11 +421,11 @@ mod sources { ); // The input file - arguments.create_and_push(implementation.file()) + arguments.create_and_push(implementation.path()) } CppCompiler::MSVC => { // The input file - arguments.create_and_push(implementation.file()); + arguments.create_and_push(implementation.path()); // The output .obj file let obj_file_path = out_dir // TODO use the helper .join(compiler.as_ref()) @@ -560,11 +441,10 @@ mod sources { } CppCompiler::GCC => { // The input file - arguments.create_and_push(implementation.file()); + arguments.create_and_push(implementation.path()); // The output file arguments.create_and_push("-o"); - let obj_file_path = - helpers::generate_impl_obj_file(compiler, out_dir, implementation); + let obj_file_path = helpers::generate_obj_file(compiler, out_dir, implementation); cache .generated_commands .add_linker_file_path(&obj_file_path); @@ -584,10 +464,8 @@ mod helpers { use chrono::{DateTime, Utc}; use super::*; - use crate::project_model::sourceset::SourceFile; - use crate::{ - bounds::TranslationUnit, cache::ZorkCache, cli::output::commands::CommandExecutionResult, - }; + use crate::utils::constants::dir_names; + use crate::{cache::ZorkCache, cli::output::commands::TranslationUnitStatus}; use std::path::PathBuf; /// Creates the path for a prebuilt module interface, based on the default expected @@ -598,7 +476,7 @@ mod helpers { /// `export module dotted.module`, in Clang, due to the expected `.pcm` extension, the final path /// will be generated as `dotted.pcm`, instead `dotted.module.pcm`. /// - /// For MSVC, we are relying in the autogeneration feature of the BMI automatically by the compiler, + /// For MSVC, we are relying on in the autogeneration feature of the BMI automatically by the compiler, /// so the output file that we need is an obj file (.obj), and not the /// binary module interface (.ifc) pub(crate) fn generate_prebuilt_miu( @@ -624,12 +502,21 @@ mod helpers { interface.module_name.to_string() }; + generate_bmi_file_path(out_dir, compiler, &mod_unit) + } + + /// Generates the [`PathBuf`] of the resultant binary module interface file of a C++ module interface + pub(crate) fn generate_bmi_file_path( + out_dir: &Path, + compiler: CppCompiler, + module_name: &str, + ) -> PathBuf { out_dir .join(compiler.as_ref()) .join("modules") .join("interfaces") .join(format!( - "{mod_unit}.{}", + "{module_name}.{}", if compiler.eq(&CppCompiler::MSVC) { compiler.get_obj_file_extension() } else { @@ -638,15 +525,17 @@ mod helpers { )) } - pub(crate) fn generate_impl_obj_file( + /// Generates the [`PathBuf`] of the resultant `.obj` file of a [`TranslationUnit`] where the + /// `.obj` file is one of the byproducts of the build process (and the one that will be sent + /// to the linker) + pub(crate) fn generate_obj_file<'a, T: TranslationUnit<'a>>( compiler: CppCompiler, out_dir: &Path, - implementation: &ModuleImplementationModel, + implementation: &T, ) -> PathBuf { out_dir .join(compiler.as_ref()) - .join("modules") - .join("implementations") + .join(dir_names::OBJECT_FILES) .join::<&str>(implementation.file_stem()) .with_extension(compiler.get_obj_file_extension()) } @@ -658,24 +547,13 @@ mod helpers { /// This is for `GCC` and `Clang` /// TODO: With the inclusion of std named modules, want we to support this anymore? pub(crate) fn build_sys_modules(model: &ZorkModel, cache: &mut ZorkCache) { - if !cache.compilers_metadata.system_modules.is_empty() { - // TODO BUG - this is not correct. - // If user later adds a new module, it won't be processed - log::info!( - "System modules already build: {:?}. They will be skipped!", - cache.compilers_metadata.system_modules - ); - } - let language_level = model.compiler.language_level_arg(); let sys_modules = model .modules - .as_ref() - .map(|modules| modules.sys_modules.clone()) - .unwrap_or_default() + .sys_modules .iter() .filter(|sys_module| { - !cache + !cache // filters all the ones that aren't on the cache .compilers_metadata .system_modules .iter() @@ -692,16 +570,11 @@ mod helpers { match model.compiler.cpp_compiler { CppCompiler::CLANG => { v.push(Argument::from("-o")); - v.push(Argument::from( - Path::new(&model.build.output_dir) - .join(model.compiler.cpp_compiler.as_ref()) - .join("modules") - .join("interfaces") - .join(sys_module.to_string()) - .with_extension( - model.compiler.cpp_compiler.get_typical_bmi_extension(), - ), - )); + v.push(Argument::from(generate_bmi_file_path( + &model.build.output_dir, + model.compiler.cpp_compiler, + sys_module, + ))); } CppCompiler::GCC => { v.push(Argument::from("-fmodules-ts")); @@ -713,13 +586,6 @@ mod helpers { }) .collect::>(); - // Maps the generated command line flags generated for every system module, - // being the key the name of the system header - // TODO: is completely unnecessary here a map. We can directly store the flags only one - // time in a list, because they will always be the same flags for every system module, - // and the system modules in another list - // Newest TODO: Can we just store them as Argument(s) in an Arguments? For example, with - // the new pre-tasks (and therefore, being cached in an unified way?) for collection_args in sys_modules { cache.generated_commands.system_modules.insert( // [3] is for the 4th flag pushed to v @@ -729,41 +595,69 @@ mod helpers { } } - /// Checks if some user declared [TranslationUnit] must be built (for example, on the first - /// iteration it will always be the case), or if the file didn't had changes since the last - /// Zork++ run and therefore, we can avoid rebuilt it - pub(crate) fn translation_unit_must_be_built( - // TODO: separation of concerns? Please - // Just make two fns, the one that checks for the status and the one that checks for modifications - // then just use a template-factory design pattern by just abstracting away the two checks in one call + /// Template factory function to call the inspectors of the status of a file on the fs that + /// is represented within `Zork++` as some kind of [`TranslationUnit`] and the status flags + /// tracked on the entities like [`SourceCommandLine::status`] and others from the [`ZorkCache`] + /// as well to determine when a concrete user declared file must be sent to the compiler in order + /// to be built, or we can skip it + /// + /// *returns: <[`TranslationUnitStatus`]>* - The state that should be set to the current + /// [`SourceCommandLine`] in order to be handled + pub(crate) fn determine_translation_unit_status( compiler: CppCompiler, last_process_execution: &DateTime, cached_source_cmd: &SourceCommandLine, - file: &Path, + ) -> TranslationUnitStatus { + // In case the user deleted the translation unit from the fs but not from the Zork++ cfg file + let translation_unit_has_been_deleted = !cached_source_cmd.path().exists(); + if translation_unit_has_been_deleted { + return TranslationUnitStatus::ToDelete; + } + + // In case the file suffered changes + let need_to_build = particular_checks_for_sent_to_build(compiler, cached_source_cmd) + || translation_unit_has_changes_on_fs(last_process_execution, cached_source_cmd); + + if need_to_build { + TranslationUnitStatus::PendingToBuild + } else { + compute_translation_unit_status(cached_source_cmd) + } + } + + /// Inspects the status field of a given [`SourceCommandLine`] of a [`TranslationUnit`] among + /// some other criteria to determine if the translation unit must be built (ex: the first iteration) + /// or rebuilt again (ex: the file is yet unprocessed because another translation unit failed before it) + pub(crate) fn particular_checks_for_sent_to_build( + compiler: CppCompiler, + cached_source_cmd: &SourceCommandLine, ) -> bool { if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { + // TODO: check on a Linux distro + // that our cache doesn't collide with the clang modules cache, or just skip clang's cache + // with a cmd arg if possible log::trace!("Module unit {:?} will be rebuilt since we've detected that you are using Clang in Windows", cached_source_cmd.path()); return true; } + false + } - let execution_result = cached_source_cmd.execution_result; - if execution_result != CommandExecutionResult::Success - && execution_result != CommandExecutionResult::Cached - // TODO: Big one. What instead of having the boolean flag of need_to_built we modify the enumerated of execution result, change its name - // to TranslationUnitStatus or some other better name pls, and we introduce a variant for mark the files that must be built? - // TODO: also, introduce a variant like PendingToBuilt, that just will be check before executing the commands? - // TODO: and even better, what if we use the new enum type as a wrapper over execution result? So we can store inside the variants - // that contains build info the execution result, and in the others maybe nothing, or other interesting data? - { - log::trace!( - "File {file:?} build process failed previously with status: {:?}. It will be rebuilt again", // TODO: technically, it can be Unprocessed, which isn't a failure - execution_result - ); - return true; - }; + // template factory function to set the real status of a translation unit (ScheduledToDelete) on the final tasks + // on the cache, and set states maybe? And what more? - // If exists and was successful, let's see if has been modified after the program last iteration + /// Checks whenever a [`TranslationUnit`] has been modified on the filesystem and its changes + /// was made *after* the last time that `Zork++` made a run. + /// + /// *returns: * - true if the target [`TranslationUnit`] has been modified after the last + /// iteration, false otherwise + pub fn translation_unit_has_changes_on_fs( + last_process_execution: &DateTime, + cached_source_cmd: &SourceCommandLine, + ) -> bool { + let file = cached_source_cmd.path(); let file_metadata = file.metadata(); + + // If exists and was successful, let's see if has been modified after the program last iteration match file_metadata { Ok(m) => match m.modified() { Ok(modified) => DateTime::::from(modified) > *last_process_execution, @@ -779,15 +673,17 @@ mod helpers { } } - pub(crate) fn generate_obj_file_path( - compiler: CppCompiler, - out_dir: &Path, - source: &SourceFile, - ) -> PathBuf { - out_dir - .join(compiler.as_ref()) - .join("sources") - .join::<&str>(source.file_stem()) - .with_extension(compiler.get_obj_file_extension()) + /// Determines which kind of [`TranslationUnitStatus`] variant must a [`SourceCommandLine`] + /// have on every process regarding specific checks and conditions before and after sent to + /// build + pub(crate) fn compute_translation_unit_status( + scl: &SourceCommandLine, + ) -> TranslationUnitStatus { + match scl.status { + TranslationUnitStatus::Success | TranslationUnitStatus::Cached => { + TranslationUnitStatus::Cached + } + _ => TranslationUnitStatus::PendingToBuild, + } } } diff --git a/zork++/src/lib/compiler/translation_unit.rs b/zork++/src/lib/compiler/translation_unit.rs deleted file mode 100644 index 31881abf..00000000 --- a/zork++/src/lib/compiler/translation_unit.rs +++ /dev/null @@ -1,8 +0,0 @@ - - -pub enum TranslationUnitKind { - ModuleInterface, - ModuleImplementation, - SourceFile, - HeaderFile -} diff --git a/zork++/src/lib/config_file/modules.rs b/zork++/src/lib/config_file/modules.rs index d44ce81c..3c8af6fd 100644 --- a/zork++/src/lib/config_file/modules.rs +++ b/zork++/src/lib/config_file/modules.rs @@ -60,7 +60,7 @@ use serde::{Deserialize, Serialize}; /// assert_eq!(&gcc_sys_headers[3], &"type_traits"); /// assert_eq!(&gcc_sys_headers[4], &"functional"); /// ``` -#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Default, PartialEq)] pub struct ModulesAttribute<'a> { #[serde(borrow)] pub base_ifcs_dir: Option<&'a str>, @@ -133,7 +133,7 @@ pub struct ModulesAttribute<'a> { /// assert_eq!(ifc_3.file, "some_module_part.cppm"); /// assert_eq!(ifc_3.module_name, Some("math_part")); /// ``` -#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Default, PartialEq)] #[serde(deny_unknown_fields)] pub struct ModuleInterface<'a> { #[serde(borrow)] @@ -159,7 +159,7 @@ pub struct ModuleInterface<'a> { /// * `is_internal_partition` - Optional field for declare that the module is actually /// a module for hold implementation details, known as module implementation partitions. /// This option only takes effect with MSVC -#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Default, PartialEq)] pub struct ModulePartition<'a> { #[serde(borrow)] pub module: &'a str, @@ -201,7 +201,7 @@ pub struct ModulePartition<'a> { /// assert_eq!(deps[1], "type_traits"); /// assert_eq!(deps[2], "iostream"); /// ``` -#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Default, PartialEq)] pub struct ModuleImplementation<'a> { #[serde(borrow)] pub file: &'a str, diff --git a/zork++/src/lib/domain/mod.rs b/zork++/src/lib/domain/mod.rs new file mode 100644 index 00000000..ff900e1c --- /dev/null +++ b/zork++/src/lib/domain/mod.rs @@ -0,0 +1,2 @@ +pub mod target; +pub mod translation_unit; diff --git a/zork++/src/lib/domain/target.rs b/zork++/src/lib/domain/target.rs new file mode 100644 index 00000000..dc8f28b0 --- /dev/null +++ b/zork++/src/lib/domain/target.rs @@ -0,0 +1,14 @@ +//! The higher abstractions of the program + +use crate::{cli::output::arguments::Argument, project_model::sourceset::SourceSet}; + +/// Bound for the user defined arguments that are passed to the compiler +pub trait ExtraArgs<'a> { + fn extra_args(&'a self) -> &'a [Argument]; +} + +/// Contracts for the executable operations +pub trait ExecutableTarget<'a>: ExtraArgs<'a> { + fn name(&'a self) -> &'a str; + fn sourceset(&'a self) -> &'a SourceSet; +} diff --git a/zork++/src/lib/domain/translation_unit.rs b/zork++/src/lib/domain/translation_unit.rs new file mode 100644 index 00000000..93c63154 --- /dev/null +++ b/zork++/src/lib/domain/translation_unit.rs @@ -0,0 +1,97 @@ +//! The module which holds the higher and generic abstractions over a source file + +use std::borrow::Cow; +use std::fmt::{Debug, Display}; +use std::path::PathBuf; +use transient::{Any, Inv}; + +/// The different type of translation units that `Zork++` is able to deal with +#[derive(Debug)] +pub enum TranslationUnitKind { + ModuleInterface, + ModuleImplementation, + SourceFile, + HeaderFile, + ModularStdLib, +} + +/// Represents any kind of translation unit and the generic operations +/// applicable to all the implementors +pub trait TranslationUnit<'a>: AsTranslationUnit<'a> + Any> + Display + Debug { + /// Returns the full path of the [`TranslationUnit`] behind the invocation, including + /// the file stem and the extension + /// + /// # Examples + /// + /// ``` + /// use std::borrow::Cow; + /// use std::path::PathBuf; + /// use zork::domain::translation_unit::TranslationUnit; + /// use zork::project_model::sourceset::SourceFile; + /// + /// let source_file = SourceFile { + /// path: PathBuf::from("/usr/include"), + /// file_stem: Cow::from("std"), + /// extension: Cow::from("h"), + /// }; + /// + /// assert_eq!(source_file.path(), PathBuf::from("/usr/include/std.h")); + /// + /// let source_file_compat = SourceFile { + /// path: PathBuf::from("/usr/include"), + /// file_stem: Cow::from("std.compat"), + /// extension: Cow::from("h"), + /// }; + /// + /// assert_eq!(source_file_compat.path(), PathBuf::from("/usr/include/std.compat.h")); + /// ``` + fn path(&self) -> PathBuf { + self.parent().join(self.filename()) + } + + /// Returns only the path to the directory where the translation unit lives on the fs + fn parent(&self) -> &PathBuf; + + /// Outputs the declared file stem (filename without extension) for this translation unit + fn file_stem(&self) -> &Cow<'_, str>; + + /// Outputs the declared extension for `self` + fn extension(&self) -> &Cow<'_, str>; + + /// Outputs the file stem concatenated with the extension for a given tu + fn filename(&self) -> String { + format!("{}.{}", self.file_stem(), self.extension()) + } +} + +/// Base trait for downcasting all the implementors of [`TranslationUnit`] when they are hidden +/// behind an opaque type +pub trait AsTranslationUnit<'a> { + fn as_any(&self) -> &dyn Any>; +} + +// Blanket implementation of [`AsTranslationUnit`] for all types implementing TranslationUnit +impl<'a, T: TranslationUnit<'a> + 'a> AsTranslationUnit<'a> for T { + fn as_any(&self) -> &dyn Any> { + self + } +} + +#[macro_export] +macro_rules! impl_translation_unit_for { + ($t:ty) => { + impl<'a> TranslationUnit<'a> for $t { + fn parent(&self) -> &PathBuf { + &self.path + } + + fn file_stem(&self) -> &Cow<'_, str> { + &self.file_stem + } + + fn extension(&self) -> &Cow<'_, str> { + &self.extension + } + } + }; +} diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 1cf987ba..157c922a 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -1,10 +1,10 @@ extern crate core; -pub mod bounds; pub mod cache; pub mod cli; pub mod compiler; pub mod config_file; +pub mod domain; pub mod project_model; pub mod utils; @@ -18,11 +18,12 @@ pub mod worker { use crate::{config_file, utils::fs::get_project_root_absolute_path}; use std::{fs, path::Path}; + use crate::utils::constants::{dir_names, error_messages}; use crate::{ cache::{self, ZorkCache}, cli::{ input::{CliArgs, Command}, - output::commands::{self, CommandExecutionResult}, + output::commands, }, compiler::generate_commands, project_model::{compiler::CppCompiler, ZorkModel}, @@ -65,32 +66,28 @@ pub mod worker { let config_files: Vec = find_config_files(project_root, &cli_args.match_files) .with_context(|| "We didn't found a valid Zork++ configuration file")?; log::trace!("Config files found: {config_files:?}"); - // TODO: add the last modified time + // TODO: add the last modified time of the cfg file for config_file in config_files { + let cfg_fn = config_file.dir_entry.file_name(); log::debug!( - "Launching a Zork++ work event for the configuration file: {:?}, located at: {:?}\n", - config_file.dir_entry.file_name(), - config_file.path + "Launching a Zork++ work event for the configuration file: {:?}", + cfg_fn, ); - let raw_file = fs::read_to_string(config_file.path).with_context(|| { - format!( - "An error happened parsing the configuration file: {:?}", - config_file.dir_entry.file_name() - ) - })?; + let raw_file = fs::read_to_string(config_file.path) + .with_context(|| format!("{}: {:?}", error_messages::READ_CFG_FILE, cfg_fn))?; let config = config_file::zork_cfg_from_file(raw_file.as_str()) - .with_context(|| "Could not parse configuration file")?; + .with_context(|| error_messages::PARSE_CFG_FILE)?; let program_data = build_model(config, cli_args, &abs_project_root)?; create_output_directory(&program_data)?; // TODO: avoid this call without check if exists - let cache = cache::load(&program_data, cli_args) - .with_context(|| "Unable to load the Zork++ cache")?; + let cache = cache::load(&program_data, cli_args)?; do_main_work_based_on_cli_input(cli_args, &program_data, cache).with_context(|| { format!( - "Failed to build the project for the config file: {:?}", + "{}: {:?}", + error_messages::FAILED_BUILD_FOR_CFG_FILE, config_file.dir_entry.file_name() ) })?; @@ -102,17 +99,22 @@ pub mod worker { /// Helper for reduce the cyclomatic complexity of the main fn. /// /// Contains the main calls to the generation of the compilers commands lines, - /// the calls to the process that runs those ones, the autorun the generated + /// the calls to the process that runs those, the autorun the generated /// binaries, the tests declared for the projects... fn do_main_work_based_on_cli_input<'a>( cli_args: &'a CliArgs, program_data: &'a ZorkModel<'_>, cache: ZorkCache, - ) -> Result { - let is_tests_run = cli_args.command.eq(&Command::Test); - + ) -> Result<()> { + // TODO: if we split the line below, we can only check for changes on the modified + // files IF and only IF the configuration files has been modified + // so we will have the need_to_rebuild in other place before the commands generation + // one option is directly on the reader, by just checking it's modification datetime (for the tu) + // + // other is to have just a separate function that only passes the required data + // like cache to be modified and the new ones let mut cache = generate_commands(program_data, cache, cli_args) - .with_context(|| "Failed to generated the commands for the project")?; + .with_context(|| error_messages::FAILURE_GENERATING_COMMANDS)?; let execution_result = match cli_args.command { Command::Build => commands::run_generated_commands(program_data, &mut cache), @@ -126,14 +128,10 @@ pub mod worker { Err(e) => Err(e), } } - _ => todo!( - "This branch should never be reached for now, as do not exists commands that may\ - trigger them. The unique remaining, is ::New, that is already processed\ - at the very beggining" - ), + _ => todo!("{}", error_messages::CLI_ARGS_CMD_NEW_BRANCH), }; - cache::save2(program_data, cache, is_tests_run)?; + cache.save(program_data)?; execution_result } @@ -156,23 +154,26 @@ pub mod worker { /// modified files, last process build time...) fn create_output_directory(model: &ZorkModel) -> Result<()> { let out_dir = &model.build.output_dir; - let compiler = &model.compiler.cpp_compiler; + let compiler: &str = model.compiler.cpp_compiler.as_ref(); + + // Recursively create the directories below and all of its parent components if they are missing + let modules_path = out_dir.join(compiler).join("modules"); - // Recursively create a directory and all of its parent components if they are missing - let modules_path = Path::new(out_dir).join(compiler.as_ref()).join("modules"); let zork_path = out_dir.join("zork"); - let zork_cache_path = zork_path.join("cache"); - let zork_intrinsics_path = zork_path.join("intrinsics"); - - utils::fs::create_directory(&modules_path.join("interfaces"))?; - utils::fs::create_directory(&modules_path.join("implementations"))?; - utils::fs::create_directory(&modules_path.join("std"))?; - utils::fs::create_directory(&out_dir.join(compiler.as_ref()).join("sources"))?; - utils::fs::create_directory(&zork_cache_path.join(model.compiler.cpp_compiler.as_ref()))?; + let zork_cache_path = zork_path.join(dir_names::CACHE); + let zork_intrinsics_path = zork_path.join(dir_names::INTRINSICS); + + utils::fs::create_directory(&out_dir.join(compiler).join(dir_names::OBJECT_FILES))?; + + utils::fs::create_directory(&modules_path.join(dir_names::INTERFACES))?; + utils::fs::create_directory(&modules_path.join(dir_names::IMPLEMENTATIONS))?; + utils::fs::create_directory(&modules_path.join(dir_names::STD))?; + + utils::fs::create_directory(&zork_cache_path)?; utils::fs::create_directory(&zork_intrinsics_path)?; // TODO: This possibly gonna be temporary - if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { + if model.compiler.cpp_compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { utils::fs::create_file( &zork_intrinsics_path, "std.h", @@ -197,13 +198,16 @@ pub mod worker { use tempfile::tempdir; use crate::config_file::{self, ZorkConfigFile}; + use crate::utils::constants::{dir_names, error_messages, ZORK}; use crate::utils::{reader::build_model, template::resources::CONFIG_FILE}; #[test] fn test_creation_directories() -> Result<()> { let temp = tempdir()?; let temp_path = temp.path(); - let out_path = temp_path.join("out"); + let out_dir = temp_path.join(dir_names::DEFAULT_OUTPUT_DIR); + + let zork_dir = out_dir.join(ZORK); let normalized_cfg_file = CONFIG_FILE .replace("", "clang") @@ -212,17 +216,29 @@ pub mod worker { let zcf: ZorkConfigFile = config_file::zork_cfg_from_file(&normalized_cfg_file)?; let cli_args = CliArgs::parse_from(["", "-vv", "run"]); let model = build_model(zcf, &cli_args, temp_path) - .with_context(|| "Error building the project model")?; + .with_context(|| error_messages::PROJECT_MODEL_MAPPING)?; + + let compiler = model.compiler.cpp_compiler; + let compiler_folder_dir = out_dir.join(compiler.as_ref()); + let modules_path = compiler_folder_dir.join("modules"); - // This should create and out/ directory in the ./zork++ folder at the root of this project + // This should create and out/ directory at the root of the tmp path super::create_output_directory(&model)?; - assert!(out_path.exists()); - assert!(out_path.join("clang").exists()); + assert!(out_dir.exists()); + + assert!(compiler_folder_dir.exists()); + + assert!(compiler_folder_dir.join(dir_names::OBJECT_FILES).exists()); + assert!(modules_path.exists()); + + assert!(modules_path.join(dir_names::INTERFACES).exists()); + assert!(modules_path.join(dir_names::IMPLEMENTATIONS).exists()); + assert!(modules_path.join(dir_names::STD).exists()); - assert!(out_path.join("zork").exists()); - assert!(out_path.join("zork").join("cache").exists()); - assert!(out_path.join("zork").join("intrinsics").exists()); + assert!(zork_dir.exists()); + assert!(zork_dir.join(dir_names::CACHE).exists()); + assert!(zork_dir.join(dir_names::INTRINSICS).exists()); Ok(()) } diff --git a/zork++/src/lib/project_model/compiler.rs b/zork++/src/lib/project_model/compiler.rs index cdd9da05..17883e19 100644 --- a/zork++/src/lib/project_model/compiler.rs +++ b/zork++/src/lib/project_model/compiler.rs @@ -1,9 +1,10 @@ use core::fmt; use std::borrow::Cow; +use crate::cli::output::arguments::Argument; use serde::{Deserialize, Serialize}; -use crate::{bounds::ExtraArgs, cli::output::arguments::Argument}; +use crate::domain::target::ExtraArgs; #[derive(Debug, PartialEq, Eq)] pub struct CompilerModel<'a> { diff --git a/zork++/src/lib/project_model/executable.rs b/zork++/src/lib/project_model/executable.rs index 523d9360..c2e48f02 100644 --- a/zork++/src/lib/project_model/executable.rs +++ b/zork++/src/lib/project_model/executable.rs @@ -1,12 +1,11 @@ use std::borrow::Cow; +use super::sourceset::SourceSet; use crate::{ - bounds::{ExecutableTarget, ExtraArgs}, cli::output::arguments::Argument, + domain::target::{ExecutableTarget, ExtraArgs}, }; -use super::sourceset::SourceSet; - #[derive(Debug, PartialEq, Eq)] pub struct ExecutableModel<'a> { pub executable_name: Cow<'a, str>, diff --git a/zork++/src/lib/project_model/mod.rs b/zork++/src/lib/project_model/mod.rs index add9d7ff..467e9046 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -19,6 +19,6 @@ pub struct ZorkModel<'a> { pub compiler: CompilerModel<'a>, pub build: BuildModel, pub executable: ExecutableModel<'a>, - pub modules: Option>, + pub modules: ModulesModel<'a>, pub tests: TestsModel<'a>, } diff --git a/zork++/src/lib/project_model/modules.rs b/zork++/src/lib/project_model/modules.rs index fddb8887..7b48a91b 100644 --- a/zork++/src/lib/project_model/modules.rs +++ b/zork++/src/lib/project_model/modules.rs @@ -4,9 +4,11 @@ use std::path::{Path, PathBuf}; use transient::Transient; -use crate::bounds::ExtraArgs; use crate::cli::output::arguments::Argument; -use crate::{bounds::TranslationUnit, config_file::modules::ModulePartition}; +use crate::config_file::modules::ModulePartition; +use crate::domain::target::ExtraArgs; +use crate::domain::translation_unit::TranslationUnit; +use crate::impl_translation_unit_for; #[derive(Debug, PartialEq, Eq)] pub struct ModulesModel<'a> { @@ -34,54 +36,21 @@ pub struct ModuleInterfaceModel<'a> { pub dependencies: Vec>, } +impl_translation_unit_for!(ModuleInterfaceModel<'a>); + impl<'a> fmt::Display for ModuleInterfaceModel<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "({:?}.{:?}., {:?}, {:?}, {:?})", - self.path, self.file_stem, self.module_name, self.dependencies, self.partition + "({:?}, {:?}, {:?}, {:?})", + self.path(), + self.module_name, + self.dependencies, + self.partition ) } } -impl<'a> TranslationUnit<'a> for ModuleInterfaceModel<'a> { - fn file(&self) -> PathBuf { - let file_name = format!("{}.{}", self.file_stem, self.extension); - self.path().join(file_name) - } - - fn path(&self) -> &PathBuf { - &self.path - } - - fn file_stem(&self) -> &Cow<'_, str> { - &self.file_stem - } - - fn extension(&self) -> &Cow<'_, str> { - &self.extension - } -} - -/* impl<'a> TranslationUnit<'a> for &'a ModuleInterfaceModel<'a> { - fn file(&self) -> PathBuf { - let file_name = format!("{}.{}", self.file_stem, self.extension); - self.path().join(file_name) - } - - fn path(&self) -> PathBuf { - self.path.clone() - } - - fn file_stem(&self) -> Cow<'_, str> { - self.file_stem.clone() - } - - fn extension(&self) -> Cow<'a, str> { - self.extension.clone() - } -} */ - #[derive(Debug, PartialEq, Eq, Transient, Clone)] pub struct ModulePartitionModel<'a> { pub module: Cow<'a, str>, @@ -99,16 +68,6 @@ impl<'a> From> for ModulePartitionModel<'a> { } } -// impl<'a> From<&ModulePartition<'a>> for ModulePartitionModel<'a> { -// fn from(value: &ModulePartition<'a>) -> &'a Self { -// Self { -// module: value.module., -// partition_name: value.partition_name.unwrap_or_default(), -// is_internal_partition: value.is_internal_partition.unwrap_or_default(), -// } -// } -// } - #[derive(Debug, PartialEq, Eq, Transient)] pub struct ModuleImplementationModel<'a> { pub path: PathBuf, @@ -117,45 +76,10 @@ pub struct ModuleImplementationModel<'a> { pub dependencies: Vec>, } +impl_translation_unit_for!(ModuleImplementationModel<'a>); + impl<'a> fmt::Display for ModuleImplementationModel<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "({:?}, {:?})", self.path, self.dependencies) - } -} - -impl<'a> TranslationUnit<'a> for ModuleImplementationModel<'a> { - fn file(&self) -> PathBuf { - let file_name = format!("{}.{}", self.file_stem, self.extension); - self.path().join(file_name) - } - - fn path(&self) -> &PathBuf { - &self.path - } - - fn file_stem(&self) -> &Cow<'_, str> { - &self.file_stem - } - - fn extension(&self) -> &Cow<'_, str> { - &self.extension + write!(f, "({:?}, {:?})", self.path(), self.dependencies) } } -/* impl<'a> TranslationUnit<'a> for &'a ModuleImplementationModel<'a> { - fn file(&self) -> PathBuf { - let file_name = format!("{}.{}", self.file_stem, self.extension); - self.path().join(file_name) - } - - fn path(&self) -> PathBuf { - self.path.clone() - } - - fn file_stem(&self) -> Cow<'_, str> { - self.file_stem.clone() - } - - fn extension(&self) -> Cow<'_, str> { - self.extension.clone() - } -} */ diff --git a/zork++/src/lib/project_model/sourceset.rs b/zork++/src/lib/project_model/sourceset.rs index ab71a330..3b066bce 100644 --- a/zork++/src/lib/project_model/sourceset.rs +++ b/zork++/src/lib/project_model/sourceset.rs @@ -1,34 +1,14 @@ use core::fmt; use std::borrow::Cow; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; -use crate::bounds::TranslationUnit; use color_eyre::{eyre::Context, Result}; use serde::{Deserialize, Serialize}; use transient::Transient; use crate::cli::output::arguments::Argument; - -// Since every file on the system has a path, this acts as a cheap conceptual -// conversion to unify PATH querying operations over anything that can be -// saved on a persistence system with an access route -pub trait File { - fn get_path(&self) -> PathBuf; -} - -impl File for Path { - fn get_path(&self) -> PathBuf { - self.to_path_buf() - } -} - -impl File for PathBuf { - fn get_path(&self) -> PathBuf { - self.to_path_buf() - } -} - -// TODO: All the trait File impl as well as the trait aren't required anymore +use crate::domain::translation_unit::TranslationUnit; +use crate::impl_translation_unit_for; #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize, Transient)] pub struct SourceFile<'a> { @@ -37,44 +17,8 @@ pub struct SourceFile<'a> { pub extension: Cow<'a, str>, } -impl<'a> TranslationUnit<'a> for SourceFile<'a> { - fn file(&self) -> PathBuf { - let file_name = format!("{}.{}", self.file_stem, self.extension); - self.path().join(file_name) - } - - fn path(&self) -> &PathBuf { - &self.path - } +impl_translation_unit_for!(SourceFile<'a>); - fn file_stem(&self) -> &Cow<'_, str> { - &self.file_stem - } - - fn extension(&self) -> &Cow<'_, str> { - &self.extension - } -} - -/* impl<'a> TranslationUnit<'a> for &'a SourceFile<'a> { - fn file(&self) -> PathBuf { - let file_name = format!("{}.{}", self.file_stem, self.extension); - self.path().join(file_name) - } - - fn path(&self) -> PathBuf { - self.path.clone() - } - - fn file_stem(&self) -> Cow<'_, str> { - self.file_stem.clone() - } - - fn extension(&self) -> Cow<'_, str> { - self.extension.clone() - } -} -*/ impl<'a> fmt::Display for SourceFile<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -120,7 +64,7 @@ pub struct SourceSet<'a> { impl<'a> SourceSet<'a> { pub fn as_args_to(&self, dst: &mut Vec) -> Result<()> { - let args = self.sources.iter().map(|sf| sf.file()).map(Argument::from); + let args = self.sources.iter().map(|sf| sf.path()).map(Argument::from); dst.extend(args); diff --git a/zork++/src/lib/project_model/tests.rs b/zork++/src/lib/project_model/tests.rs index 0261991b..e0947bd1 100644 --- a/zork++/src/lib/project_model/tests.rs +++ b/zork++/src/lib/project_model/tests.rs @@ -1,6 +1,6 @@ use crate::{ - bounds::{ExecutableTarget, ExtraArgs}, cli::output::arguments::Argument, + domain::target::{ExecutableTarget, ExtraArgs}, }; use std::borrow::Cow; diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index 18403c83..a59f7824 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -1,8 +1,37 @@ //! Constant value definitions to use across the whole program +pub const ZORK: &str = "zork"; + +/// The names of the `Zork++`specific directories, not their paths +pub mod dir_names { + pub const DEFAULT_OUTPUT_DIR: &str = "out"; + pub const CACHE: &str = "cache"; + pub const STD: &str = "std"; + pub const INTRINSICS: &str = "intrinsics"; + pub const INTERFACES: &str = "interfaces"; + pub const IMPLEMENTATIONS: &str = "implementations"; + pub const OBJECT_FILES: &str = "obj_files"; +} + +pub mod error_messages { + pub const READ_CFG_FILE: &str = "Could not read the configuration file"; + pub const PARSE_CFG_FILE: &str = "Could not parse the configuration file"; + pub const FAILURE_GENERATING_COMMANDS: &str = + "Failed to generated the commands for the project"; + pub const FAILED_BUILD_FOR_CFG_FILE: &str = "Failed to build the project for the config file"; + pub const GENERAL_ARGS_NOT_FOUND: &str = "Something went wrong loading the general arguments"; + pub const PROJECT_MODEL_MAPPING: &str = "Error building the project model"; + pub const COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND: &str = + "Something went wrong loading the general arguments"; + pub const CLI_ARGS_CMD_NEW_BRANCH: &str = + "This branch should never be reached for now, as do not exists commands that may\ + trigger them. The unique remaining, is ::New, that is already processed\ + at the very beggining"; +} + pub const CONFIG_FILE_NAME: &str = "zork"; -pub const CONFIG_FILE_EXT: &str = ".toml"; -pub const DEFAULT_OUTPUT_DIR: &str = "out"; +pub const CONFIG_FILE_EXT: &str = "toml"; +pub const CACHE_FILE_EXT: &str = "json"; pub const BINARY_EXTENSION: &str = if cfg!(target_os = "windows") { "exe" diff --git a/zork++/src/lib/utils/fs.rs b/zork++/src/lib/utils/fs.rs index fb89ae0a..2ff3d325 100644 --- a/zork++/src/lib/utils/fs.rs +++ b/zork++/src/lib/utils/fs.rs @@ -8,8 +8,6 @@ use std::{ }; use walkdir::WalkDir; -use super::constants; - /// Creates a new file in the filesystem if the given does not exists yet at the specified location pub fn create_file<'a>(path: &Path, filename: &'a str, buff_write: &'a [u8]) -> Result<()> { let file_path = path.join(filename); @@ -90,7 +88,7 @@ where T: Serialize + ?Sized, { serde_json::to_writer_pretty( - File::create(path).with_context(|| "Error creating the cache file")?, + File::create(path).with_context(|| "Error opening the cache file")?, data, ) .with_context(|| "Error serializing data to the cache") @@ -101,9 +99,7 @@ where T: for<'a> Deserialize<'a> + Default, P: AsRef, { - let buffer = BufReader::new( - File::open(path.as_ref().join(constants::ZORK_CACHE_FILENAME)) - .with_context(|| "Error opening the cache file")?, - ); - Ok(serde_json::from_reader(buffer).unwrap_or_default()) + let buffer = + BufReader::new(File::open(path.as_ref()).with_context(|| "Error opening the cache file")?); + Ok(serde_json::from_reader(buffer).expect("Unable to parse the Zork++ cache file")) } diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 95c4ecb9..3cfc36d5 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -30,7 +30,7 @@ use std::borrow::Cow; use std::path::{Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; -use super::constants::DEFAULT_OUTPUT_DIR; +use super::constants::dir_names; /// Details about a found configuration file on the project /// @@ -166,7 +166,7 @@ fn assemble_build_model(config: Option, project_root: &Path) -> .as_ref() .and_then(|build| build.output_dir) .map(|out_dir| out_dir.strip_prefix("./").unwrap_or(out_dir)) - .unwrap_or(DEFAULT_OUTPUT_DIR); + .unwrap_or(dir_names::DEFAULT_OUTPUT_DIR); BuildModel { output_dir: Path::new(project_root).join(output_dir), @@ -189,8 +189,7 @@ fn assemble_executable_model<'a>( let sources = config .and_then(|exe| exe.sources.as_ref()) .map(|srcs| { - srcs.iter() // TODO: abstract this kind of procedures away to some method of TranslationUnit, for example? - // or some other new trait (can't this have a default impl on the trait definition itself? + srcs.iter() .map(|src| Cow::Borrowed(*src)) .collect::>>() }) @@ -213,9 +212,8 @@ fn assemble_executable_model<'a>( fn assemble_modules_model<'a>( config: Option>, project_root: &Path, -) -> Option> { - config.as_ref()?; // early guard - let modules = config.unwrap(); +) -> ModulesModel<'a> { + let modules = config.unwrap_or_default(); let base_ifcs_dir = modules .base_ifcs_dir @@ -266,14 +264,14 @@ fn assemble_modules_model<'a>( .map(|args| args.iter().map(|arg| Argument::from(*arg)).collect()) .unwrap_or_default(); - Some(ModulesModel { + ModulesModel { base_ifcs_dir, interfaces, base_impls_dir, implementations, sys_modules, extra_args, - }) + } } fn assemble_module_interface_model<'a>( @@ -466,7 +464,14 @@ mod test { sourceset: SourceSet { sources: vec![] }, extra_args: vec![], }, - modules: None, + modules: ModulesModel { + base_ifcs_dir: Path::new("."), + interfaces: vec![], + base_impls_dir: Path::new("."), + implementations: vec![], + sys_modules: vec![], + extra_args: vec![], + }, tests: TestsModel { test_executable_name: "Zork++_test".into(), sourceset: SourceSet { sources: vec![] }, @@ -509,7 +514,7 @@ mod test { sourceset: SourceSet { sources: vec![] }, extra_args: vec![Argument::from("-Werr")], }, - modules: Some(ModulesModel { + modules: ModulesModel { base_ifcs_dir: Path::new("ifcs"), interfaces: vec![ ModuleInterfaceModel { @@ -546,7 +551,7 @@ mod test { ], sys_modules: vec!["iostream".into()], extra_args: vec![Argument::from("-Wall")], - }), + }, tests: TestsModel { test_executable_name: "zork_check".into(), sourceset: SourceSet { sources: vec![] }, diff --git a/zork++/src/lib/utils/template/mod.rs b/zork++/src/lib/utils/template/mod.rs index 89a7eb0e..80bf4c1b 100644 --- a/zork++/src/lib/utils/template/mod.rs +++ b/zork++/src/lib/utils/template/mod.rs @@ -108,7 +108,7 @@ pub fn create_templated_project( utils::fs::create_file( &project_root, &format!( - "{}_{}{}", + "{}_{}.{}", utils::constants::CONFIG_FILE_NAME, compiler.as_ref(), utils::constants::CONFIG_FILE_EXT From 919d1f5c0ac92e02c46c4c84b87755b99683aa19 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 10 Jul 2024 21:17:24 +0200 Subject: [PATCH 28/73] fix: missed function invocations on the benchmarks module --- zork++/benches/benchmarks.rs | 5 +++-- zork++/src/lib/cache/mod.rs | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/zork++/benches/benchmarks.rs b/zork++/benches/benchmarks.rs index 7e8af8e3..49b31aae 100644 --- a/zork++/benches/benchmarks.rs +++ b/zork++/benches/benchmarks.rs @@ -11,16 +11,17 @@ use zork::{ config_file::{self, ZorkConfigFile}, utils::{self, reader::build_model}, }; +use zork::compiler::generate_commands; pub fn build_project_benchmark(c: &mut Criterion) { let config: ZorkConfigFile = config_file::zork_cfg_from_file(utils::constants::CONFIG_FILE_MOCK).unwrap(); let cli_args = CliArgs::parse(); let program_data = build_model(config, &cli_args, Path::new(".")).unwrap(); - let mut cache = ZorkCache::default(); + let cache = ZorkCache::default(); c.bench_function("Build project", |b| { - b.iter(|| build_project(black_box(&program_data), black_box(&mut cache), false)) + b.iter(|| generate_commands(black_box(&program_data), black_box(cache), &cli_args)) }); c.bench_function("Cache loading time", |b| { diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 40b0a9f5..db066080 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -267,8 +267,7 @@ pub type EnvVars = HashMap; #[derive(Deserialize, Serialize, Debug, Default, Clone)] pub struct CompilersMetadata<'a> { - // TODO: apply the same solution a have a fat pointer or better convert them into a Union/enum? - // ALL of them must be optional, since only exists + // TODO: apply the same solution: have a fat pointer or better convert them into a Union/enum? pub msvc: MsvcMetadata<'a>, pub clang: ClangMetadata, pub gcc: GccMetadata, From 683444d244adeb32b133c2c7de5bef8185e96e15 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 10 Jul 2024 21:22:57 +0200 Subject: [PATCH 29/73] fix: changed the arguments passed to the bencher function on the measures of the 'Generate Commands' --- zork++/benches/benchmarks.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/zork++/benches/benchmarks.rs b/zork++/benches/benchmarks.rs index 49b31aae..2c4cd4da 100644 --- a/zork++/benches/benchmarks.rs +++ b/zork++/benches/benchmarks.rs @@ -7,7 +7,6 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use zork::{ cache::{self, ZorkCache}, cli::input::CliArgs, - compiler::build_project, config_file::{self, ZorkConfigFile}, utils::{self, reader::build_model}, }; @@ -18,10 +17,9 @@ pub fn build_project_benchmark(c: &mut Criterion) { config_file::zork_cfg_from_file(utils::constants::CONFIG_FILE_MOCK).unwrap(); let cli_args = CliArgs::parse(); let program_data = build_model(config, &cli_args, Path::new(".")).unwrap(); - let cache = ZorkCache::default(); - c.bench_function("Build project", |b| { - b.iter(|| generate_commands(black_box(&program_data), black_box(cache), &cli_args)) + c.bench_function("Generate commands", |b| { + b.iter(|| generate_commands(black_box(&program_data), black_box(ZorkCache::default()), &cli_args)) }); c.bench_function("Cache loading time", |b| { From 9994f25846b82b9e5a34b3b4a4a1b2a5768216b2 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 10 Jul 2024 21:23:38 +0200 Subject: [PATCH 30/73] chore: cargo fmt --- zork++/benches/benchmarks.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/zork++/benches/benchmarks.rs b/zork++/benches/benchmarks.rs index 2c4cd4da..4de2a6d9 100644 --- a/zork++/benches/benchmarks.rs +++ b/zork++/benches/benchmarks.rs @@ -4,13 +4,13 @@ use std::path::Path; use clap::Parser; use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use zork::compiler::generate_commands; use zork::{ cache::{self, ZorkCache}, cli::input::CliArgs, config_file::{self, ZorkConfigFile}, utils::{self, reader::build_model}, }; -use zork::compiler::generate_commands; pub fn build_project_benchmark(c: &mut Criterion) { let config: ZorkConfigFile = @@ -19,7 +19,13 @@ pub fn build_project_benchmark(c: &mut Criterion) { let program_data = build_model(config, &cli_args, Path::new(".")).unwrap(); c.bench_function("Generate commands", |b| { - b.iter(|| generate_commands(black_box(&program_data), black_box(ZorkCache::default()), &cli_args)) + b.iter(|| { + generate_commands( + black_box(&program_data), + black_box(ZorkCache::default()), + &cli_args, + ) + }) }); c.bench_function("Cache loading time", |b| { From 41342743757de345cf9efe3bf427067e5c47e643 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 12 Jul 2024 15:46:29 +0200 Subject: [PATCH 31/73] feat: refactor of the C++ modular std lib procedure chore: code reorganization and clean-up --- zork++/src/lib/cache/mod.rs | 192 ++++----- zork++/src/lib/cli/output/arguments.rs | 34 +- zork++/src/lib/cli/output/commands.rs | 65 +-- zork++/src/lib/compiler/data_factory.rs | 48 +-- zork++/src/lib/compiler/mod.rs | 504 ++++++++++++---------- zork++/src/lib/domain/translation_unit.rs | 5 +- zork++/src/lib/project_model/compiler.rs | 17 +- zork++/src/lib/project_model/modules.rs | 20 +- zork++/src/lib/utils/constants.rs | 9 +- zork++/src/lib/utils/reader.rs | 14 +- zork++/test/test.rs | 2 +- 11 files changed, 480 insertions(+), 430 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index db066080..ff420dbc 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -23,10 +23,11 @@ use crate::{ output::commands::{Commands, SourceCommandLine}, }, project_model::{compiler::CppCompiler, ZorkModel}, - utils::{self, constants::GCC_CACHE_DIR}, + utils::{self}, }; use serde::{Deserialize, Serialize}; -use walkdir::WalkDir; + +use crate::project_model::compiler::StdLibMode; /// Standalone utility for load from the file system the Zork++ cache file /// for the target [`CppCompiler`] @@ -93,39 +94,84 @@ impl<'a> ZorkCache<'a> { TranslationUnitKind::ModuleInterface => self.get_module_ifc_cmd(translation_unit), TranslationUnitKind::ModuleImplementation => self.get_module_impl_cmd(translation_unit), TranslationUnitKind::SourceFile => self.get_source_cmd(translation_unit), - TranslationUnitKind::ModularStdLib => todo!(), - TranslationUnitKind::HeaderFile => todo!(), + TranslationUnitKind::SystemHeader => self.get_system_module_cmd(translation_unit), + TranslationUnitKind::ModularStdLib(stdlib_mode) => match stdlib_mode { + StdLibMode::Cpp => self.get_cpp_stdlib_cmd(), + StdLibMode::CCompat => self.get_ccompat_stdlib_cmd(), + }, } } fn get_module_ifc_cmd>( &mut self, - module_interface_model: &T, + module_interface: &T, ) -> Option<&mut SourceCommandLine> { self.generated_commands .interfaces .iter_mut() - .find(|mi| module_interface_model.path().eq(&mi.path())) + .find(|cached_tu| module_interface.path().eq(&cached_tu.path())) } fn get_module_impl_cmd>( &mut self, - module_impl_model: &T, + module_impl: &T, ) -> Option<&mut SourceCommandLine> { self.generated_commands .implementations .iter_mut() - .find(|mi| *module_impl_model.path() == (*mi).path()) + .find(|cached_tu| module_impl.path().eq(&cached_tu.path())) } fn get_source_cmd>( &mut self, - module_impl_model: &T, + source: &T, ) -> Option<&mut SourceCommandLine> { self.generated_commands .sources .iter_mut() - .find(|mi| module_impl_model.path() == (*mi).path()) + .find(|cached_tu| source.path().eq(&cached_tu.path())) + } + + /// Gets the target [`SystemModule`] generated [`SourceCommandLine`] from the cache + /// + /// TODO: Since we don't implement the lookup of the directory of the installed system headers, + /// we are using some tricks to matching the generated command, but is not robust + fn get_system_module_cmd>( + &mut self, + system_module: &T, + ) -> Option<&mut SourceCommandLine> { + self.generated_commands + .system_modules + .iter_mut() + .find(|cached_tu| system_module.file_stem().eq(cached_tu.filename())) + } + + pub fn get_cpp_stdlib_cmd_by_kind( + &mut self, + stdlib_mode: StdLibMode, + ) -> Option<&mut SourceCommandLine> { + match stdlib_mode { + StdLibMode::Cpp => self.generated_commands.cpp_stdlib.as_mut(), + StdLibMode::CCompat => self.generated_commands.c_compat_stdlib.as_mut(), + } + } + + pub fn set_cpp_stdlib_cmd_by_kind( + &mut self, + stdlib_mode: StdLibMode, + cmd_line: Option, + ) { + match stdlib_mode { + StdLibMode::Cpp => self.generated_commands.cpp_stdlib = cmd_line, + StdLibMode::CCompat => self.generated_commands.c_compat_stdlib = cmd_line, + } + } + fn get_cpp_stdlib_cmd(&mut self) -> Option<&mut SourceCommandLine> { + self.generated_commands.cpp_stdlib.as_mut() + } + + fn get_ccompat_stdlib_cmd(&mut self) -> Option<&mut SourceCommandLine> { + self.generated_commands.c_compat_stdlib.as_mut() } /// The tasks associated with the cache after load it from the file system @@ -135,14 +181,6 @@ impl<'a> ZorkCache<'a> { msvc::load_metadata(self, program_data)? } - if compiler != CppCompiler::MSVC - && helpers::user_declared_system_headers_to_build(program_data) - { - let i = Self::track_system_modules(program_data); - self.compilers_metadata.system_modules.clear(); - self.compilers_metadata.system_modules.extend(i); - } - Ok(()) } @@ -152,15 +190,6 @@ impl<'a> ZorkCache<'a> { compile_commands::map_generated_commands_to_compilation_db(self)?; } - if !(program_data.compiler.cpp_compiler == CppCompiler::MSVC) { - self.compilers_metadata.system_modules = program_data - .modules - .sys_modules - .iter() - .map(|e| e.to_string()) - .collect::>(); - } - Ok(()) } @@ -198,59 +227,6 @@ impl<'a> ZorkCache<'a> { + 2 // the cpp_stdlib and the c_compat_stdlib // + 1 // TODO: the linker one? Does it supports it clangd? } - - /// Looks for the already precompiled `GCC` or `Clang` system headers, - /// to avoid recompiling them on every process - /// NOTE: This feature should be deprecated and therefore, removed from Zork++ when GCC and - /// Clang fully implement the required procedures to build the C++ std library as a module - fn track_system_modules<'b: 'a>( - // TODO move it to helpers - program_data: &'b ZorkModel<'b>, - ) -> impl Iterator + 'b { - let root = if program_data.compiler.cpp_compiler == CppCompiler::GCC { - Path::new(GCC_CACHE_DIR).to_path_buf() - } else { - program_data - .build - .output_dir - .join("clang") - .join("modules") - .join("interfaces") - }; - - WalkDir::new(root) - .into_iter() - .filter_map(Result::ok) - .filter(|file| { - if file - .metadata() - .expect("Error retrieving metadata") - .is_file() - { - program_data // TODO: review this, since it's too late and I am just satisfying the borrow checker - .modules - .sys_modules - .iter() - .any(|sys_mod| { - file.file_name() - .to_str() - .unwrap() - .starts_with(&sys_mod.to_string()) - }) - } else { - false - } - }) - .map(|dir_entry| { - dir_entry - .file_name() - .to_str() - .unwrap() - .split('.') - .collect::>()[0] - .to_string() - }) - } } #[derive(Deserialize, Serialize, Debug, Default, Clone)] @@ -271,32 +247,24 @@ pub struct CompilersMetadata<'a> { pub msvc: MsvcMetadata<'a>, pub clang: ClangMetadata, pub gcc: GccMetadata, - pub system_modules: Vec, // TODO: This hopefully will dissappear soon - // TODO: Vec of Cow } #[derive(Deserialize, Serialize, Debug, Default, Clone)] pub struct MsvcMetadata<'a> { pub compiler_version: Option, pub dev_commands_prompt: Option, - pub vs_stdlib_path: Option>, // std.ixx path for the MSVC std lib location - pub vs_c_stdlib_path: Option>, // std.compat.ixx path for the MSVC std lib location - pub stdlib_bmi_path: PathBuf, // BMI byproduct after build in it at the target out dir of + pub vs_stdlib_path: SourceFile<'a>, // std.ixx path for the MSVC std lib location + pub vs_ccompat_stdlib_path: SourceFile<'a>, // std.compat.ixx path for the MSVC std lib location + pub stdlib_bmi_path: PathBuf, // BMI byproduct after build in it at the target out dir of // the user pub stdlib_obj_path: PathBuf, // Same for the .obj file // Same as the ones defined for the C++ std lib, but for the C std lib - pub c_stdlib_bmi_path: PathBuf, - pub c_stdlib_obj_path: PathBuf, + pub ccompat_stdlib_bmi_path: PathBuf, + pub ccompat_stdlib_obj_path: PathBuf, // The environmental variables that will be injected to the underlying invoking shell pub env_vars: EnvVars, } -impl<'a> MsvcMetadata<'_> { - pub fn is_loaded(&'a self) -> bool { - self.dev_commands_prompt.is_some() && self.vs_stdlib_path.is_some() - } -} - #[derive(Deserialize, Serialize, Debug, Default, Clone)] pub struct ClangMetadata { pub env_vars: EnvVars, @@ -309,12 +277,13 @@ pub struct GccMetadata { /// Helper procedures to process cache data for Microsoft's MSVC mod msvc { - use crate::cache::{msvc, ZorkCache}; + use crate::cache::ZorkCache; use crate::project_model::sourceset::SourceFile; use crate::project_model::ZorkModel; use crate::utils; use crate::utils::constants; - use color_eyre::eyre::{Context, OptionExt}; + use crate::utils::constants::error_messages; + use color_eyre::eyre::{eyre, Context, ContextCompat, OptionExt}; use regex::Regex; use std::borrow::Cow; use std::collections::HashMap; @@ -354,22 +323,31 @@ mod msvc { .output() .with_context(|| "Unable to load MSVC pre-requisites. Please, open an issue with the details on upstream")?; - msvc.env_vars = msvc::load_env_vars_from_cmd_output(&output.stdout)?; + msvc.env_vars = load_env_vars_from_cmd_output(&output.stdout)?; // Cloning the useful ones for quick access at call site msvc.compiler_version = msvc.env_vars.get("VisualStudioVersion").cloned(); - let vs_stdlib_path = - Path::new(msvc.env_vars.get("VCToolsInstallDir").unwrap()).join("modules"); - msvc.vs_stdlib_path = Some(SourceFile { + // Check the existence of the VCtools + let vctools_dir = msvc + .env_vars + .get("VCToolsInstallDir") + .with_context(|| error_messages::msvc::MISSING_VCTOOLS_DIR)?; + + let vs_stdlib_path = Path::new(vctools_dir).join("modules"); + if !vs_stdlib_path.exists() { + return Err(eyre!(error_messages::msvc::STDLIB_MODULES_NOT_FOUND)); + } + + msvc.vs_stdlib_path = SourceFile { path: vs_stdlib_path.clone(), file_stem: Cow::Borrowed("std"), extension: compiler.default_module_extension(), - }); - msvc.vs_c_stdlib_path = Some(SourceFile { + }; + msvc.vs_ccompat_stdlib_path = SourceFile { path: vs_stdlib_path, file_stem: Cow::Borrowed("std.compat"), extension: compiler.default_module_extension(), - }); + }; let modular_stdlib_byproducts_path = Path::new(&program_data.build.output_dir) .join(compiler.as_ref()) .join("modules") @@ -385,9 +363,9 @@ mod msvc { let c_modular_stdlib_byproducts_path = modular_stdlib_byproducts_path; let compat = String::from("compat."); // TODO: find a better way - msvc.c_stdlib_bmi_path = c_modular_stdlib_byproducts_path + msvc.ccompat_stdlib_bmi_path = c_modular_stdlib_byproducts_path .with_extension(compat.clone() + compiler.get_typical_bmi_extension()); - msvc.c_stdlib_obj_path = c_modular_stdlib_byproducts_path + msvc.ccompat_stdlib_obj_path = c_modular_stdlib_byproducts_path .with_extension(compat + compiler.get_obj_file_extension()); } @@ -418,14 +396,8 @@ mod msvc { mod helpers { use super::*; - use crate::project_model::ZorkModel; use std::path::PathBuf; - // TODO: this can be used also on the compiler/mod.rs - pub(crate) fn user_declared_system_headers_to_build(program_data: &ZorkModel<'_>) -> bool { - !program_data.modules.sys_modules.is_empty() - } - pub(crate) fn initialize_default_cache<'a>( compiler: CppCompiler, cache_file_path: PathBuf, diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index 451dd7f9..43772ab7 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -43,7 +43,7 @@ impl From> for Argument { impl From<&Cow<'_, str>> for Argument { fn from(value: &Cow<'_, str>) -> Self { - Self(value.clone().into()) // TODO: review this + Self(value.clone().into()) } } @@ -126,19 +126,13 @@ impl Arguments { /// Creates and stores a new [`Argument`] to the end of this collection /// from any type *T* that can be coerced into an [`Argument`] type - pub fn create_and_push(&mut self, val: T) + pub fn push(&mut self, val: T) where T: Into, { self.0.push(val.into()) } - /// Appends a new [`Argument`] to the end of this collection - pub fn push(&mut self, arg: Argument) { - self.0.push(arg) - } // TODO: aren't this one and the one above redundant? Wouldn't be better to unify both - // interfaces in only one method call? With a better name, btw? Like or - /// Given an optional, adds the inner value if there's Some(<[Argument]>) pub fn push_opt(&mut self, arg: Option) { if let Some(val) = arg { @@ -292,32 +286,24 @@ pub mod msvc_args { let (stdlib_sf, stdlib_bmi_path, stdlib_obj_path) = if stdlib_mode.eq(&StdLibMode::Cpp) { ( - msvc.vs_stdlib_path.as_ref().unwrap(), + &msvc.vs_stdlib_path, &msvc.stdlib_bmi_path, &msvc.stdlib_obj_path, ) } else { ( - msvc.vs_c_stdlib_path.as_ref().unwrap(), - &msvc.c_stdlib_bmi_path, - &msvc.c_stdlib_obj_path, + &msvc.vs_ccompat_stdlib_path, + &msvc.ccompat_stdlib_bmi_path, + &msvc.ccompat_stdlib_obj_path, ) }; - arguments.create_and_push("/W4"); - - arguments.create_and_push("/reference"); - arguments.create_and_push(format! { - "std={}", msvc.stdlib_bmi_path.display() - }); - - arguments.create_and_push("/c"); - arguments.create_and_push(stdlib_sf.path()); - arguments.create_and_push("/ifcOutput"); - arguments.create_and_push(format! { + arguments.push(stdlib_sf.path()); + arguments.push("/ifcOutput"); + arguments.push(format! { "{}", stdlib_bmi_path.display() }); - arguments.create_and_push(format! { + arguments.push(format! { "/Fo{}", stdlib_obj_path.display() }); diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index c57e54a3..82a19978 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -1,7 +1,6 @@ //! Contains helpers and data structures to be processed in a nice and neat way the commands generated to be executed //! by Zork++ -use std::collections::HashMap; use std::ffi::OsStr; use std::fmt::Debug; use std::slice::Iter; @@ -35,13 +34,6 @@ pub fn run_generated_commands( let (general_args, compiler_specific_shared_args, env_vars) = load_common_data(cache)?; - for sys_module in &cache.generated_commands.system_modules { - // TODO: will be deprecated soon, hopefully - // But while isn't deleted, we could normalize them into SourceCommandLine - // And then, consider to join them into the all generated commands iter - execute_command(program_data, sys_module.1, &env_vars)?; - } - let translation_units = cache .generated_commands .get_all_command_lines() @@ -78,7 +70,7 @@ pub fn run_generated_commands( } } - log::debug!("Processing the linker command line..."); + log::info!("Processing the linker command line..."); let r = execute_command( program_data, &general_args @@ -165,9 +157,26 @@ where .with_context(|| format!("[{compiler}] - Command {arguments} failed!")) } -/// for some translation unit /// -/// * args* : member that holds all the cmd arguments that will be passed to the compiler driver +pub trait CommandLine { + fn args(&self) -> &Arguments; +} + +impl CommandLine for SourceCommandLine { + fn args(&self) -> &Arguments { + &self.args + } +} + +/// Type for representing the command line that will be sent to the target compiler, and +/// store its different components +/// +/// * directory*: the path where the translation unit lives +/// * filename*: the translation unit declared name on the fs with the extension +/// * args*: member that holds all the cmd arguments that will be passed to the compiler driver +/// * status*: A [`TranslationUnitStatus`] that represents all the different phases that a source command +/// line can have among all the different iterations of the program, changing according to the modifications +/// over the translation unit in the fs and the result of the build execution #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SourceCommandLine { pub directory: PathBuf, @@ -177,7 +186,6 @@ pub struct SourceCommandLine { } impl SourceCommandLine { - // TODO T instead of &T? pub fn new<'a, T: TranslationUnit<'a>>(tu: &T, args: Arguments) -> Self { Self { directory: PathBuf::from(tu.parent()), @@ -190,6 +198,10 @@ impl SourceCommandLine { pub fn path(&self) -> PathBuf { self.directory.join(Path::new(&self.filename)) } + + pub fn filename(&self) -> &String { + &self.filename + } } #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -213,25 +225,19 @@ impl LinkerCommandLine { /// Saves the path at which a compilation product of any translation unit will be placed, /// in order to add it to the files that will be linked to generate the final product /// in the two-phase compilation model - pub fn add_buildable_at(&mut self, path: &Path) { - self.byproducts.push(Argument::from(path)); - } - - /// Owned version of TODO link - pub fn add_owned_buildable_at(&mut self, path: PathBuf) { - self.byproducts.push(path.into()); + pub fn add_byproduct_path(&mut self, path: PathBuf) { + self.byproducts.push(path); } } /// Holds the generated command line arguments for a concrete compiler #[derive(Serialize, Deserialize, Default, Debug)] pub struct Commands { - pub compiler: CppCompiler, // TODO: review if we can afford this field given the new - // architechture pub cpp_stdlib: Option, pub c_compat_stdlib: Option, - pub system_modules: HashMap, - + // pub system_modules: HashMap, + pub system_modules: Vec, // TODO: SourceCommandLine while we found a better approach + // or while we don't implement the parser that gets the path to the compilers std library headers pub general_args: Option, pub compiler_common_args: Option>, @@ -254,18 +260,14 @@ impl Commands { .as_mut_slice() .iter_mut() .chain(self.c_compat_stdlib.as_mut_slice().iter_mut()) - // TODO: chain pre-tasks (system headers) + .chain(self.system_modules.as_mut_slice().iter_mut()) .chain(self.interfaces.as_mut_slice().iter_mut()) .chain(self.implementations.as_mut_slice().iter_mut()) .chain(self.sources.as_mut_slice().iter_mut()) } - pub fn add_linker_file_path(&mut self, path: &Path) { - self.linker.add_buildable_at(path); - } - - pub fn add_linker_file_path_owned(&mut self, path: PathBuf) { - self.linker.add_owned_buildable_at(path); + pub fn add_linker_file_path(&mut self, path: PathBuf) { + self.linker.add_byproduct_path(path); } } @@ -273,8 +275,7 @@ impl core::fmt::Display for Commands { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "Commands for [{}]:\n- Interfaces: {:?},\n- Implementations: {:?},\n- Sources: {:?}", - self.compiler, + "Commands:\n- Interfaces: {:?},\n- Implementations: {:?},\n- Sources: {:?}", collect_source_command_line(self.interfaces.iter()), collect_source_command_line(self.implementations.iter()), collect_source_command_line(self.sources.iter()) diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index 09651740..ef298955 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -2,7 +2,7 @@ //! translation unit, having shared data without replicating it until the final command line must //! be generated in order to be stored (in cache) and executed (in the underlying shell) -use std::{borrow::Cow, path::Path, rc::Rc}; +use std::{borrow::Cow, path::Path}; use serde::{Deserialize, Serialize}; @@ -15,6 +15,10 @@ use crate::{ }; /// Holds the common arguments across all the different command lines regarding the target compiler +/// +/// Even that the arguments are written according the named value for each one depending on the compiler, +/// the ones held here are meant to be here because every supported compiler will use them, while the +/// compiler args specific structs are holding the ones that are required depending on the compiler #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct CommonArgs(Arguments); impl CommonArgs { @@ -25,20 +29,11 @@ impl CommonArgs { pub fn get_args_slice(&self) -> impl Iterator { self.0.as_slice().iter() } - - pub fn get_args_slice_rced(&self) -> impl Iterator> { - self.0.as_slice().iter().map(Rc::new) - } } impl<'a> From<&'a ZorkModel<'_>> for CommonArgs { fn from(model: &'a ZorkModel<'_>) -> Self { let mut common_args = Arguments::default(); - // TODO: - // Aren't the common args in the end compiler specific ones? - // Should we remove this DS? - // Ah no. Maybe we can use it for hold things like -o (shared among all three (MSVC also - // accepts - in some args instead of /)) common_args.push(model.compiler.language_level_arg()); common_args.extend_from_slice(model.compiler.extra_args()); @@ -70,9 +65,6 @@ pub fn compiler_common_arguments_factory( } } -// TODO: the specific ones, like the object file... can we just create a prototype -// function - /// Allows to have a common interface for any type that represents a data structure which its /// purpose is to hold common [`Argument`] across the diferent kind of [`TranslationUnit`] #[typetag::serde(tag = "type")] @@ -95,9 +87,9 @@ impl CompilerCommonArguments for ClangCommonArgs { fn get_args(&self) -> Arguments { let mut args = Arguments::default(); args.push(self.std_lib.as_arg()); - args.create_and_push(&self.implicit_modules); - args.create_and_push(&self.implicit_module_map); - args.create_and_push(&self.prebuilt_module_path); + args.push(&self.implicit_modules); + args.push(&self.implicit_module_map); + args.push(&self.prebuilt_module_path); args } } @@ -106,17 +98,17 @@ impl CompilerCommonArguments for ClangCommonArgs { impl CompilerCommonArguments for MsvcCommonArgs { fn get_args(&self) -> Arguments { let mut args = Arguments::default(); - args.create_and_push(&self.exception_handling_model); - args.create_and_push(&self.no_logo); - args.create_and_push(&self.ifc_search_dir); - args.create_and_push(&*self.ifc_search_dir_value); + args.push(&self.exception_handling_model); + args.push(&self.no_logo); + args.push(&self.ifc_search_dir); + args.push(&*self.ifc_search_dir_value); - args.create_and_push("/reference"); - args.create_and_push(format! { + args.push("/reference"); + args.push(format! { "std={}", self.stdlib_ref_path.display() }); - args.create_and_push("/reference"); - args.create_and_push(format! { + args.push("/reference"); + args.push(format! { "std.compat={}", self.c_compat_stdlib_ref_path.display() }); args @@ -127,7 +119,7 @@ impl CompilerCommonArguments for MsvcCommonArgs { impl CompilerCommonArguments for GccCommonArgs { fn get_args(&self) -> Arguments { let mut args = Arguments::default(); - args.create_and_push("-fmodules-ts"); + args.push("-fmodules-ts"); args } } @@ -181,7 +173,11 @@ impl MsvcCommonArgs { ), stdlib_ref_path: Cow::Owned(cache.compilers_metadata.msvc.stdlib_bmi_path.clone()), c_compat_stdlib_ref_path: Cow::Owned( - cache.compilers_metadata.msvc.c_stdlib_bmi_path.clone(), + cache + .compilers_metadata + .msvc + .ccompat_stdlib_bmi_path + .clone(), ), } } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 752bfcbc..21ea1017 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -3,20 +3,17 @@ //! operating system against the designed compilers in the configuration //! file. -pub mod data_factory; +use std::path::Path; use color_eyre::Result; -use std::path::Path; use crate::cli::output::commands::TranslationUnitStatus; +use crate::project_model::modules::SystemModule; use crate::{ cache::ZorkCache, cli::{ input::{CliArgs, Command}, - output::{ - arguments::{msvc_args, Argument, Arguments}, - commands::SourceCommandLine, - }, + output::{arguments::Argument, commands::SourceCommandLine}, }, domain::{ target::ExecutableTarget, @@ -33,6 +30,8 @@ use crate::{ use self::data_factory::CommonArgs; +pub mod data_factory; + /// The core procedure. Generates the commands that will be sent to the compiler /// for every translation unit declared by the user for its project pub fn generate_commands<'a>( @@ -44,11 +43,11 @@ pub fn generate_commands<'a>( load_flyweights_for_general_shared_data(model, &mut cache); // Build the std library as a module - generate_modular_stdlibs_cmds(model, &mut cache); + generate_modular_stdlibs_cmds(model, &mut cache, cli_args); // Pre-tasks if model.compiler.cpp_compiler != CppCompiler::MSVC && !model.modules.sys_modules.is_empty() { - helpers::build_sys_modules(model, &mut cache) + generate_sys_modules_commands(model, &mut cache, cli_args); } // Translation units and linker @@ -79,38 +78,36 @@ fn load_flyweights_for_general_shared_data(model: &ZorkModel, cache: &mut ZorkCa /// Generates the cmds for build the C++ standard libraries (std and std.compat) according to the specification /// of each compiler vendor -fn generate_modular_stdlibs_cmds(model: &ZorkModel<'_>, cache: &mut ZorkCache) { - let compiler = model.compiler.cpp_compiler; - let lpe = cache.metadata.last_program_execution; - - // TODO: remaining ones: Clang, GCC - if compiler.eq(&CppCompiler::MSVC) { - if let Some(cpp_stdlib_cmd) = cache.generated_commands.cpp_stdlib.as_mut() { - cpp_stdlib_cmd.status = - helpers::determine_translation_unit_status(compiler, &lpe, cpp_stdlib_cmd); - } else { - log::info!( - "Generating the command for build the {:?} C++ standard library implementation", - compiler - ); - let cpp_stdlib_cmd = msvc_args::generate_std_cmd(cache, StdLibMode::Cpp); - cache.generated_commands.cpp_stdlib = Some(cpp_stdlib_cmd); - } - - if let Some(ccompat_stdlib_cmd) = cache.generated_commands.c_compat_stdlib.as_mut() { - ccompat_stdlib_cmd.status = - helpers::determine_translation_unit_status(compiler, &lpe, ccompat_stdlib_cmd); - } else { - log::info!( - "Generating the command for build the {:?} C++ standard library implementation", - compiler - ); - let ccompat_stdlib = msvc_args::generate_std_cmd(cache, StdLibMode::CCompat); - cache.generated_commands.c_compat_stdlib = Some(ccompat_stdlib); - } +fn generate_modular_stdlibs_cmds(model: &ZorkModel, cache: &mut ZorkCache, _cli_args: &CliArgs) { + // TODO: remaining ones: Clang, GCC. + // NOTE: Provisionally 'If' guarded because only MSVC is supported now to build the + // C++ standard library implementations + if model.compiler.cpp_compiler.eq(&CppCompiler::MSVC) { + modules::generate_modular_cpp_stdlib_args(model, cache, StdLibMode::Cpp); + modules::generate_modular_cpp_stdlib_args(model, cache, StdLibMode::CCompat); } } +/// Procedure to generate the commands for the system headers of their standard C++ library +/// for a given compiler +/// +/// These commands are the ones that allows to translate C++ standard headers to named modules +fn generate_sys_modules_commands<'a>( + model: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + cli_args: &'a CliArgs, +) { + process_kind_translation_units( + model, + cache, + cli_args, + &model.modules.sys_modules, + TranslationUnitKind::SystemHeader, + ); +} + +/// The procedure that takes care of generating the [`SourceCommandLine`] to build the user's declared +/// C++ standard names modules fn process_modules<'a>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, @@ -139,6 +136,13 @@ fn process_modules<'a>( Ok(()) } +/// Processor for generate the commands of the non-modular translation units +/// +/// *NOTE*: This will be changed on the future, when we decide how we should architecture the implementation +/// of named targets +/// +/// *IMPL_NOTE*: Consider in the future if it's worth to maintain two paths for build module implementations +/// and source, since they are basically (almost) the same thing fn generate_sources_cmds_args<'a>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, @@ -226,89 +230,100 @@ pub fn generate_linker_general_command_line_args<'a>( /// The core procedure of the commands generation process. /// -/// It takes care +/// It takes care of generate the [`SourceCommandLine`] for a set of given implementors of [`TranslationUnit`], +/// processing them according to the passed [`TranslationUnitKind`] discriminator if the command doesn't exist, +/// otherwise, it will handle the need of tracking individually every translation unit in every program iteration +/// (while the cache isn't purged by the user) to set their [`TranslationUnitStatus`] flag, which ultimately +/// decides on every run if the file must be sent to build to the target [`CppCompiler`] fn process_kind_translation_units<'a, T: TranslationUnit<'a>>( model: &ZorkModel<'_>, cache: &mut ZorkCache<'a>, cli_args: &CliArgs, translation_units: &'a [T], for_kind: TranslationUnitKind, +) { + translation_units.iter().for_each(|translation_unit| { + process_kind_translation_unit(model, cache, cli_args, translation_unit, &for_kind) + }); +} + +fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( + model: &ZorkModel<'_>, + cache: &mut ZorkCache<'a>, + cli_args: &CliArgs, + translation_unit: &'a T, + for_kind: &TranslationUnitKind, ) { let compiler = cache.compiler; let lpe = cache.metadata.last_program_execution; - translation_units.iter().for_each(|translation_unit| { - if let Some(generated_cmd) = cache.get_cmd_for_translation_unit_kind(translation_unit, &for_kind) { - let build_translation_unit = helpers::determine_translation_unit_status(compiler, &lpe, generated_cmd); + if let Some(generated_cmd) = cache.get_cmd_for_translation_unit_kind(translation_unit, for_kind) + { + let build_translation_unit = + helpers::determine_translation_unit_status(compiler, &lpe, generated_cmd); - if build_translation_unit.ne(&TranslationUnitStatus::PendingToBuild) { - log::trace!("Source file:{:?} was not modified since the last iteration. No need to rebuilt it again.", &translation_unit.path()); - } + if build_translation_unit.ne(&TranslationUnitStatus::PendingToBuild) { + log::trace!("Source file: {:?} was not modified since the last iteration. No need to rebuilt it again.", &translation_unit.path()); + } - generated_cmd.status = build_translation_unit; - } else { - let tu_with_erased_type = translation_unit.as_any(); - match for_kind { - TranslationUnitKind::ModuleInterface => { - let resolved_tu = transient::Downcast::downcast_ref::(tu_with_erased_type); - sources::generate_module_interface_cmd(model, cache, resolved_tu.unwrap()); - }, - TranslationUnitKind::ModuleImplementation => sources::generate_module_implementation_cmd(model, cache, transient::Downcast::downcast_ref::(tu_with_erased_type).unwrap()), - TranslationUnitKind::SourceFile => { - let target = if cli_args.command.eq(&Command::Test) { &model.tests as &dyn ExecutableTarget } else { &model.executable as &dyn ExecutableTarget }; - sources::generate_sources_arguments(model, cache, transient::Downcast::downcast_ref::(tu_with_erased_type).unwrap(), target) - } - _ => todo!() + generated_cmd.status = build_translation_unit; + } else { + let tu_with_erased_type = translation_unit.as_any(); + // TODO: remove the .unwrap() (s) below for some other robust solution + match &for_kind { + TranslationUnitKind::ModuleInterface => { + let resolved_tu = + transient::Downcast::downcast_ref::(tu_with_erased_type); + modules::generate_module_interface_cmd(model, cache, resolved_tu.unwrap()); } - }; - }); + TranslationUnitKind::ModuleImplementation => { + modules::generate_module_implementation_cmd( + model, + cache, + transient::Downcast::downcast_ref::( + tu_with_erased_type, + ) + .unwrap(), + ) + } + TranslationUnitKind::SourceFile => { + let target = if cli_args.command.eq(&Command::Test) { + &model.tests as &dyn ExecutableTarget + } else { + &model.executable as &dyn ExecutableTarget + }; + sources::generate_sources_arguments( + model, + cache, + transient::Downcast::downcast_ref::(tu_with_erased_type).unwrap(), + target, + ) + } + TranslationUnitKind::SystemHeader => modules::generate_sys_module_cmd( + model, + cache, + transient::Downcast::downcast_ref::(tu_with_erased_type).unwrap(), + ), + _ => todo!(), + } + }; } -/// Specific operations over source files -mod sources { +/// Command line arguments generators procedures for C++ standard modules +mod modules { use std::path::Path; - use super::helpers; use crate::cache::ZorkCache; - use crate::cli::output::arguments::Arguments; - use crate::domain::target::{ExecutableTarget, ExtraArgs}; + use crate::cli::output::arguments::{clang_args, msvc_args, Arguments}; + use crate::cli::output::commands::{SourceCommandLine, TranslationUnitStatus}; + use crate::compiler::helpers; + use crate::compiler::helpers::generate_bmi_file_path; use crate::domain::translation_unit::TranslationUnit; - use crate::project_model::modules::ModuleImplementationModel; - use crate::project_model::sourceset::SourceFile; - use crate::{ - cli::output::{arguments::clang_args, commands::SourceCommandLine}, - project_model::{compiler::CppCompiler, modules::ModuleInterfaceModel, ZorkModel}, + use crate::project_model::compiler::{CppCompiler, StdLibMode}; + use crate::project_model::modules::{ + ModuleImplementationModel, ModuleInterfaceModel, SystemModule, }; - - /// Generates the command line arguments for non-module source files - pub fn generate_sources_arguments<'a>( - model: &'a ZorkModel, - cache: &mut ZorkCache, - source: &'a SourceFile, - target: &'a (impl ExecutableTarget<'a> + ?Sized), - ) { - let compiler = model.compiler.cpp_compiler; - let out_dir = model.build.output_dir.as_ref(); - - let mut arguments = Arguments::default(); - arguments.extend_from_slice(model.compiler.extra_args()); - arguments.extend_from_slice(target.extra_args()); - - let obj_file = helpers::generate_obj_file(compiler, out_dir, source); - let fo = if compiler.eq(&CppCompiler::MSVC) { - "/Fo" - } else { - "-o" - }; - arguments.create_and_push(format!("{fo}{}", obj_file.display())); - arguments.create_and_push(source.path()); - - let command_line = SourceCommandLine::new(source, arguments); - cache.generated_commands.sources.push(command_line); - cache - .generated_commands - .add_linker_file_path_owned(obj_file) - } + use crate::project_model::ZorkModel; /// Generates the expected arguments for precompile the BMIs depending on self pub fn generate_module_interface_cmd<'a>( @@ -316,16 +331,18 @@ mod sources { cache: &'a mut ZorkCache, interface: &'a ModuleInterfaceModel, ) { + let mut arguments = Arguments::default(); let compiler = model.compiler.cpp_compiler; let out_dir: &Path = model.build.output_dir.as_ref(); - let mut arguments = Arguments::default(); + // The Path of the generated binary module interface + let binary_module_ifc = helpers::generate_prebuilt_miu(compiler, out_dir, interface); match compiler { CppCompiler::CLANG => { - arguments.create_and_push("-x"); - arguments.create_and_push("c++-module"); - arguments.create_and_push("--precompile"); + arguments.push("-x"); + arguments.push("c++-module"); + arguments.push("--precompile"); clang_args::add_direct_module_interfaces_dependencies( &interface.dependencies, compiler, @@ -333,65 +350,53 @@ mod sources { &mut arguments, ); - // The resultant BMI as a .pcm file - arguments.create_and_push("-o"); - // The output file - let obj_file = helpers::generate_prebuilt_miu(compiler, out_dir, interface); - arguments.create_and_push(&obj_file); - cache - .generated_commands - .add_linker_file_path_owned(obj_file); - // The input file - arguments.create_and_push(interface.path()); + // The generated BMI + arguments.push("-o"); + arguments.push(&binary_module_ifc); } CppCompiler::MSVC => { - arguments.create_and_push("/ifcOutput"); + arguments.push("/ifcOutput"); let implicit_lookup_mius_path = out_dir .join(compiler.as_ref()) .join("modules") - .join("interfaces"); // TODO: as shared arg for kind interfaces? - arguments.create_and_push(implicit_lookup_mius_path); + .join("interfaces"); + arguments.push(implicit_lookup_mius_path); // The output .obj file - let obj_file = helpers::generate_prebuilt_miu(compiler, out_dir, interface); - arguments.create_and_push(format!("/Fo{}", obj_file.display())); - cache - .generated_commands - .add_linker_file_path_owned(obj_file); + arguments.push(format!("/Fo{}", binary_module_ifc.display())); if let Some(partition) = &interface.partition { if partition.is_internal_partition { - arguments.create_and_push("/internalPartition"); + arguments.push("/internalPartition"); } else { - arguments.create_and_push("/interface"); + arguments.push("/interface"); } } else { - arguments.create_and_push("/interface"); + arguments.push("/interface"); } - arguments.create_and_push("/TP"); - // The input file - arguments.create_and_push(interface.path()) + arguments.push("/TP"); } CppCompiler::GCC => { - arguments.create_and_push("-x"); - arguments.create_and_push("c++"); - // The input file - arguments.create_and_push(interface.path()); + arguments.push("-x"); + arguments.push("c++"); // The output file - arguments.create_and_push("-o"); - let obj_file = helpers::generate_prebuilt_miu(compiler, out_dir, interface); - arguments.create_and_push(&obj_file); - cache - .generated_commands - .add_linker_file_path_owned(obj_file); + arguments.push("-o"); + arguments.push(&binary_module_ifc); } } + // The input file + arguments.push(interface.path()); + + cache + .generated_commands + .add_linker_file_path(binary_module_ifc); + let cmd_line = SourceCommandLine::new(interface, arguments); cache.generated_commands.interfaces.push(cmd_line); } - /// Generates the expected arguments for compile the implementation module files + /// Generates the required arguments for compile the implementation module files pub fn generate_module_implementation_cmd<'a>( model: &'a ZorkModel, cache: &mut ZorkCache, @@ -401,17 +406,16 @@ mod sources { let out_dir = model.build.output_dir.as_ref(); let mut arguments = Arguments::default(); - arguments.extend_from_slice(model.compiler.extra_args()); + + // The input file + arguments.push(implementation.path()); + let obj_file_path = helpers::generate_obj_file(compiler, out_dir, implementation); match compiler { CppCompiler::CLANG => { // The resultant object file - arguments.create_and_push("-o"); - let obj_file_path = helpers::generate_obj_file(compiler, out_dir, implementation); - cache - .generated_commands - .add_linker_file_path(&obj_file_path); - arguments.create_and_push(obj_file_path); + arguments.push("-o"); + arguments.push(&obj_file_path); clang_args::add_direct_module_interfaces_dependencies( &implementation.dependencies, @@ -419,54 +423,151 @@ mod sources { out_dir, &mut arguments, ); - - // The input file - arguments.create_and_push(implementation.path()) } CppCompiler::MSVC => { - // The input file - arguments.create_and_push(implementation.path()); // The output .obj file - let obj_file_path = out_dir // TODO use the helper - .join(compiler.as_ref()) - .join("modules") - .join("implementations") - .join::<&str>(implementation.file_stem()) - .with_extension(compiler.get_obj_file_extension()); - - cache - .generated_commands - .add_linker_file_path(&obj_file_path); - arguments.create_and_push(format!("/Fo{}", obj_file_path.display())); + arguments.push(format!("/Fo{}", obj_file_path.display())); } CppCompiler::GCC => { - // The input file - arguments.create_and_push(implementation.path()); // The output file - arguments.create_and_push("-o"); - let obj_file_path = helpers::generate_obj_file(compiler, out_dir, implementation); - cache - .generated_commands - .add_linker_file_path(&obj_file_path); - arguments.create_and_push(obj_file_path); + arguments.push("-o"); + arguments.push(&obj_file_path); } } + cache.generated_commands.add_linker_file_path(obj_file_path); + let cmd = SourceCommandLine::new(implementation.to_owned(), arguments); cache.generated_commands.implementations.push(cmd); } + + /// System headers can be imported as modules, but they must be built before being imported. + /// + /// This feature is supported by `GCC` and `Clang` + /// NOTE: With the inclusion of std named modules, want we to support this anymore? + pub(crate) fn generate_sys_module_cmd( + model: &ZorkModel, + cache: &mut ZorkCache, + sys_module: &SystemModule, + ) { + let sys_module_name = &sys_module.file_stem; + let generated_bmi_path = generate_bmi_file_path( + &model.build.output_dir, + model.compiler.cpp_compiler, + sys_module_name, + ); + + let mut args = Arguments::default(); + args.push("-x"); + args.push("c++-system-header"); + args.push(sys_module_name); + + match model.compiler.cpp_compiler { + CppCompiler::CLANG => { + args.push("-o"); + args.push(&generated_bmi_path); + } + CppCompiler::GCC => { + // `GCC` system headers built as modules goes directly to their `gcm.cache` + args.push("-fmodules-ts"); + } + _ => {} + }; + + let cmd = SourceCommandLine { + directory: generated_bmi_path, // TODO: we are using the generated byproduct as the path for checking for its + // existence instead of the system header one while we don't implement the feature that inspects where the standard + // libraries lives. Isn't ideal, but serves the basic purpose of regenerating the commands when they it's needed, and + // system headers aren't unlikely to change (unless an stdlib update) + filename: sys_module.to_string(), + args, + status: TranslationUnitStatus::PendingToBuild, + }; + cache.generated_commands.system_modules.push(cmd); + } + + pub(crate) fn generate_modular_cpp_stdlib_args( + model: &ZorkModel, + cache: &mut ZorkCache, + stdlib_mode: StdLibMode, + ) { + let compiler = model.compiler.cpp_compiler; + let lpe = cache.metadata.last_program_execution; + + if let Some(cpp_stdlib_cmd) = cache.get_cpp_stdlib_cmd_by_kind(stdlib_mode) { + cpp_stdlib_cmd.status = + helpers::determine_translation_unit_status(compiler, &lpe, cpp_stdlib_cmd); + } else { + let compiler = model.compiler.cpp_compiler; + log::info!( + "Generating the command for build the {:?} {}", + compiler, + stdlib_mode.printable_info() + ); + + let scl = msvc_args::generate_std_cmd(cache, stdlib_mode); + cache.set_cpp_stdlib_cmd_by_kind(stdlib_mode, Some(scl)); + // TODO: see the Some(scl) above? well, implement the generators for the other compilers + // and just return optional none?, so we can get rid out of all the todo + } + } +} + +/// Specific operations over source files +mod sources { + use crate::cache::ZorkCache; + use crate::cli::output::arguments::Arguments; + use crate::domain::target::ExecutableTarget; + use crate::domain::translation_unit::TranslationUnit; + use crate::project_model::sourceset::SourceFile; + use crate::{ + cli::output::commands::SourceCommandLine, + project_model::{compiler::CppCompiler, ZorkModel}, + }; + + use super::helpers; + + /// Generates the command line arguments for non-module source files + pub fn generate_sources_arguments<'a>( + model: &'a ZorkModel, + cache: &mut ZorkCache, + source: &'a SourceFile, + target: &'a (impl ExecutableTarget<'a> + ?Sized), + ) { + let compiler = model.compiler.cpp_compiler; + let out_dir = model.build.output_dir.as_ref(); + + let mut arguments = Arguments::default(); + arguments.extend_from_slice(target.extra_args()); + + let obj_file = helpers::generate_obj_file(compiler, out_dir, source); + let fo = if compiler.eq(&CppCompiler::MSVC) { + "/Fo" + } else { + "-o" + }; + arguments.push(format!("{fo}{}", obj_file.display())); + arguments.push(source.path()); + + let command_line = SourceCommandLine::new(source, arguments); + cache.generated_commands.sources.push(command_line); + cache.generated_commands.add_linker_file_path(obj_file) + } } /// Helpers for reduce the cyclomatic complexity introduced by the /// kind of workflow that should be done with this parse, format and /// generate. mod helpers { + use std::path::PathBuf; + use chrono::{DateTime, Utc}; - use super::*; + use crate::cli::output::commands::TranslationUnitStatus; + use crate::utils::constants::dir_names; - use crate::{cache::ZorkCache, cli::output::commands::TranslationUnitStatus}; - use std::path::PathBuf; + + use super::*; /// Creates the path for a prebuilt module interface, based on the default expected /// extension for BMI's given a compiler @@ -476,7 +577,7 @@ mod helpers { /// `export module dotted.module`, in Clang, due to the expected `.pcm` extension, the final path /// will be generated as `dotted.pcm`, instead `dotted.module.pcm`. /// - /// For MSVC, we are relying on in the autogeneration feature of the BMI automatically by the compiler, + /// For MSVC, we are relying on the auto generation feature of the BMI automatically by the compiler, /// so the output file that we need is an obj file (.obj), and not the /// binary module interface (.ifc) pub(crate) fn generate_prebuilt_miu( @@ -540,61 +641,6 @@ mod helpers { .with_extension(compiler.get_obj_file_extension()) } - /// System headers as can be imported as modules must be built before being imported. - /// First it will compare with the elements stored in the cache, and only we will - /// generate commands for the non processed elements yet. - /// - /// This is for `GCC` and `Clang` - /// TODO: With the inclusion of std named modules, want we to support this anymore? - pub(crate) fn build_sys_modules(model: &ZorkModel, cache: &mut ZorkCache) { - let language_level = model.compiler.language_level_arg(); - let sys_modules = model - .modules - .sys_modules - .iter() - .filter(|sys_module| { - !cache // filters all the ones that aren't on the cache - .compilers_metadata - .system_modules - .iter() - .any(|s| s.eq(*sys_module)) - }) - .map(|sys_module| { - let mut v = vec![ - language_level.clone(), - Argument::from("-x"), - Argument::from("c++-system-header"), - Argument::from(sys_module), - ]; - - match model.compiler.cpp_compiler { - CppCompiler::CLANG => { - v.push(Argument::from("-o")); - v.push(Argument::from(generate_bmi_file_path( - &model.build.output_dir, - model.compiler.cpp_compiler, - sys_module, - ))); - } - CppCompiler::GCC => { - v.push(Argument::from("-fmodules-ts")); - } - _ => {} - } - - v - }) - .collect::>(); - - for collection_args in sys_modules { - cache.generated_commands.system_modules.insert( - // [3] is for the 4th flag pushed to v - collection_args[3].value().to_string(), - Arguments::from_vec(collection_args), - ); - } - } - /// Template factory function to call the inspectors of the status of a file on the fs that /// is represented within `Zork++` as some kind of [`TranslationUnit`] and the status flags /// tracked on the entities like [`SourceCommandLine::status`] and others from the [`ZorkCache`] diff --git a/zork++/src/lib/domain/translation_unit.rs b/zork++/src/lib/domain/translation_unit.rs index 93c63154..e0471b42 100644 --- a/zork++/src/lib/domain/translation_unit.rs +++ b/zork++/src/lib/domain/translation_unit.rs @@ -1,5 +1,6 @@ //! The module which holds the higher and generic abstractions over a source file +use crate::project_model::compiler::StdLibMode; use std::borrow::Cow; use std::fmt::{Debug, Display}; use std::path::PathBuf; @@ -11,8 +12,8 @@ pub enum TranslationUnitKind { ModuleInterface, ModuleImplementation, SourceFile, - HeaderFile, - ModularStdLib, + ModularStdLib(StdLibMode), + SystemHeader, } /// Represents any kind of translation unit and the generic operations diff --git a/zork++/src/lib/project_model/compiler.rs b/zork++/src/lib/project_model/compiler.rs index 17883e19..b47bdc49 100644 --- a/zork++/src/lib/project_model/compiler.rs +++ b/zork++/src/lib/project_model/compiler.rs @@ -187,8 +187,23 @@ impl AsRef for StdLib { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum StdLibMode { Cpp, //< The C++ STD library implemented for every vendor CCompat, //< Same, but extending it with the C ISO standard library } + +impl StdLibMode { + pub fn printable_info(&self) -> &str { + match self { + StdLibMode::Cpp => "C++ standard library implementation", + StdLibMode::CCompat => "C++ C compat standard library implementation", + } + } +} + +impl fmt::Display for StdLibMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.printable_info()) + } +} diff --git a/zork++/src/lib/project_model/modules.rs b/zork++/src/lib/project_model/modules.rs index 7b48a91b..c542e8ef 100644 --- a/zork++/src/lib/project_model/modules.rs +++ b/zork++/src/lib/project_model/modules.rs @@ -16,7 +16,7 @@ pub struct ModulesModel<'a> { pub interfaces: Vec>, pub base_impls_dir: &'a Path, pub implementations: Vec>, - pub sys_modules: Vec>, + pub sys_modules: Vec>, pub extra_args: Vec, } @@ -83,3 +83,21 @@ impl<'a> fmt::Display for ModuleImplementationModel<'a> { write!(f, "({:?}, {:?})", self.path(), self.dependencies) } } + +/// Holds the fs information about the `C++` system headers, which they can be built as +/// binary module interface for certain compilers, while allowing to import those system headers +/// as modules +#[derive(Debug, PartialEq, Eq, Default, Transient)] +pub struct SystemModule<'a> { + pub path: PathBuf, + pub file_stem: Cow<'a, str>, + pub extension: Cow<'a, str>, +} + +impl_translation_unit_for!(SystemModule<'a>); + +impl<'a> fmt::Display for SystemModule<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.path()) + } +} diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index a59f7824..c9d737cf 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -26,7 +26,14 @@ pub mod error_messages { pub const CLI_ARGS_CMD_NEW_BRANCH: &str = "This branch should never be reached for now, as do not exists commands that may\ trigger them. The unique remaining, is ::New, that is already processed\ - at the very beggining"; + at the very beginning"; + + pub mod msvc { + pub const STDLIB_MODULES_NOT_FOUND: &str = + "Can't find the MSVC standard library modules. Did you installed them?"; + pub const MISSING_VCTOOLS_DIR: &str = + "Unable to find MSVC VCToolsInstallDir. Did you installed the required C++ tools for the compiler?"; + } } pub const CONFIG_FILE_NAME: &str = "zork"; diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 3cfc36d5..c162b8a7 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -1,4 +1,6 @@ use crate::cli::input::CliArgs; + +use crate::project_model::modules::SystemModule; use crate::project_model::sourceset::SourceFile; use crate::{ cli::output::arguments::Argument, @@ -254,11 +256,14 @@ fn assemble_modules_model<'a>( .map_or_else(Default::default, |headers| { headers .iter() - .map(|sys_header| Cow::from(*sys_header)) + .map(|sys_header| SystemModule { + file_stem: Cow::from(*sys_header), + ..Default::default() + }) .collect() }); - let extra_args = modules // TODO: this has to dissappear from the Zork++ build options + let extra_args = modules // TODO: this has to disappear from the Zork++ build options .extra_args .as_ref() .map(|args| args.iter().map(|arg| Argument::from(*arg)).collect()) @@ -549,7 +554,10 @@ mod test { dependencies: vec!["iostream".into()], }, ], - sys_modules: vec!["iostream".into()], + sys_modules: vec![SystemModule { + file_stem: Cow::Borrowed("iostream"), + ..Default::default() + }], extra_args: vec![Argument::from("-Wall")], }, tests: TestsModel { diff --git a/zork++/test/test.rs b/zork++/test/test.rs index 262de897..c45b883a 100644 --- a/zork++/test/test.rs +++ b/zork++/test/test.rs @@ -114,7 +114,7 @@ fn test_msvc_full_process() -> Result<()> { #[cfg(target_os = "windows")] #[test] -#[ignore] +#[ignore] // TODO: we need to add the `MSYS2` project on the Windows GitHub VM's fn test_gcc_windows_full_process() -> Result<()> { let project_name = "gcc_example"; From 27a99bb5b5e0d760a83090c0d47970fb64ba682e Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 12 Jul 2024 17:21:29 +0200 Subject: [PATCH 32/73] feat: Argument has now Cow as the inner type --- zork++/src/lib/cache/compile_commands.rs | 16 +-- zork++/src/lib/cache/mod.rs | 22 +-- zork++/src/lib/cli/output/arguments.rs | 108 +++++++-------- zork++/src/lib/cli/output/commands.rs | 147 ++++++++++----------- zork++/src/lib/compiler/data_factory.rs | 10 +- zork++/src/lib/compiler/mod.rs | 70 +++++----- zork++/src/lib/lib.rs | 6 +- zork++/src/lib/project_model/compiler.rs | 2 +- zork++/src/lib/project_model/executable.rs | 2 +- zork++/src/lib/project_model/modules.rs | 2 +- zork++/src/lib/project_model/tests.rs | 2 +- zork++/src/lib/utils/reader.rs | 2 +- 12 files changed, 188 insertions(+), 201 deletions(-) diff --git a/zork++/src/lib/cache/compile_commands.rs b/zork++/src/lib/cache/compile_commands.rs index c1b5095d..4a2cef26 100644 --- a/zork++/src/lib/cache/compile_commands.rs +++ b/zork++/src/lib/cache/compile_commands.rs @@ -8,14 +8,14 @@ use serde::Serialize; use std::fs::File; use std::path::{Path, PathBuf}; -pub type CompileCommands = Vec; +pub type CompileCommands<'a> = Vec>; /// Generates the `compile_commands.json` file, that acts as a compilation database /// for some static analysis external tools, like `clang-tidy`, and populates it with /// the generated commands for the translation units -pub(crate) fn map_generated_commands_to_compilation_db( - cache: &ZorkCache, -) -> Result { +pub(crate) fn map_generated_commands_to_compilation_db<'a>( + cache: &'a ZorkCache<'a>, +) -> Result> { log::trace!("Generating the compilation database..."); let generated_commands = cache.get_all_commands_iter(); @@ -41,14 +41,14 @@ pub(crate) fn map_generated_commands_to_compilation_db( /// Data model for serialize the data that will be outputted /// to the `compile_commands.json` compilation database file #[derive(Serialize, Debug, Default, Clone)] -pub struct CompileCommand { +pub struct CompileCommand<'a> { pub directory: PathBuf, pub file: String, - pub arguments: Arguments, + pub arguments: Arguments<'a>, } -impl From<&SourceCommandLine> for CompileCommand { - fn from(value: &SourceCommandLine) -> Self { +impl<'a> From<&SourceCommandLine<'a>> for CompileCommand<'a> { + fn from(value: &SourceCommandLine<'a>) -> Self { let value = value.clone(); Self { directory: value.directory, diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index ff420dbc..ca6e50ba 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -72,7 +72,7 @@ pub fn load<'a>(program_data: &'a ZorkModel<'_>, cli_args: &CliArgs) -> Result { pub compiler: CppCompiler, pub compilers_metadata: CompilersMetadata<'a>, - pub generated_commands: Commands, + pub generated_commands: Commands<'a>, pub metadata: CacheMetadata, } @@ -89,7 +89,7 @@ impl<'a> ZorkCache<'a> { &mut self, translation_unit: &T, translation_unit_kind: &TranslationUnitKind, - ) -> Option<&mut SourceCommandLine> { + ) -> Option<&mut SourceCommandLine<'a>> { match translation_unit_kind { TranslationUnitKind::ModuleInterface => self.get_module_ifc_cmd(translation_unit), TranslationUnitKind::ModuleImplementation => self.get_module_impl_cmd(translation_unit), @@ -105,7 +105,7 @@ impl<'a> ZorkCache<'a> { fn get_module_ifc_cmd>( &mut self, module_interface: &T, - ) -> Option<&mut SourceCommandLine> { + ) -> Option<&mut SourceCommandLine<'a>> { self.generated_commands .interfaces .iter_mut() @@ -115,7 +115,7 @@ impl<'a> ZorkCache<'a> { fn get_module_impl_cmd>( &mut self, module_impl: &T, - ) -> Option<&mut SourceCommandLine> { + ) -> Option<&mut SourceCommandLine<'a>> { self.generated_commands .implementations .iter_mut() @@ -125,7 +125,7 @@ impl<'a> ZorkCache<'a> { fn get_source_cmd>( &mut self, source: &T, - ) -> Option<&mut SourceCommandLine> { + ) -> Option<&mut SourceCommandLine<'a>> { self.generated_commands .sources .iter_mut() @@ -139,7 +139,7 @@ impl<'a> ZorkCache<'a> { fn get_system_module_cmd>( &mut self, system_module: &T, - ) -> Option<&mut SourceCommandLine> { + ) -> Option<&mut SourceCommandLine<'a>> { self.generated_commands .system_modules .iter_mut() @@ -149,7 +149,7 @@ impl<'a> ZorkCache<'a> { pub fn get_cpp_stdlib_cmd_by_kind( &mut self, stdlib_mode: StdLibMode, - ) -> Option<&mut SourceCommandLine> { + ) -> Option<&mut SourceCommandLine<'a>> { match stdlib_mode { StdLibMode::Cpp => self.generated_commands.cpp_stdlib.as_mut(), StdLibMode::CCompat => self.generated_commands.c_compat_stdlib.as_mut(), @@ -159,18 +159,18 @@ impl<'a> ZorkCache<'a> { pub fn set_cpp_stdlib_cmd_by_kind( &mut self, stdlib_mode: StdLibMode, - cmd_line: Option, + cmd_line: Option>, ) { match stdlib_mode { StdLibMode::Cpp => self.generated_commands.cpp_stdlib = cmd_line, StdLibMode::CCompat => self.generated_commands.c_compat_stdlib = cmd_line, } } - fn get_cpp_stdlib_cmd(&mut self) -> Option<&mut SourceCommandLine> { + fn get_cpp_stdlib_cmd(&mut self) -> Option<&mut SourceCommandLine<'a>> { self.generated_commands.cpp_stdlib.as_mut() } - fn get_ccompat_stdlib_cmd(&mut self) -> Option<&mut SourceCommandLine> { + fn get_ccompat_stdlib_cmd(&mut self) -> Option<&mut SourceCommandLine<'a>> { self.generated_commands.c_compat_stdlib.as_mut() } @@ -195,7 +195,7 @@ impl<'a> ZorkCache<'a> { /// Method that returns the HashMap that holds the environmental variables that must be passed /// to the underlying shell - pub fn get_process_env_args(&self) -> &EnvVars { + pub fn get_process_env_args(&'a mut self) -> &'a EnvVars { match self.compiler { CppCompiler::MSVC => &self.compilers_metadata.msvc.env_vars, CppCompiler::CLANG => &self.compilers_metadata.clang.env_vars, diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index 43772ab7..6f5c8b97 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -12,10 +12,10 @@ use crate::project_model::compiler::LanguageLevel; /// Wrapper type for represent and storing a command line argument #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Argument(String); +pub struct Argument<'a>(Cow<'a, str>); -impl Argument { - pub fn value(&self) -> &String { +impl<'a> Argument<'a> { + pub fn value(&self) -> &Cow<'a, str> { &self.0 } pub fn is_empty(&self) -> bool { @@ -23,79 +23,71 @@ impl Argument { } } -impl From<&str> for Argument { - fn from(value: &str) -> Self { - Self(value.into()) +impl<'a> From<&'a str> for Argument<'a> { + fn from(value: &'a str) -> Self { + Self(Cow::Borrowed(value)) } } -impl From<&String> for Argument { - fn from(value: &String) -> Self { - Self(value.into()) +impl<'a> From<&'a String> for Argument<'a> { + fn from(value: &'a String) -> Self { + Self(Cow::Borrowed(value)) } } -impl From> for Argument { - fn from(value: Cow<'_, str>) -> Self { - Self(value.into()) - } +impl<'a> From> for Argument<'a> { + fn from(value: Cow<'a, str>) -> Self { Self(value) } } -impl From<&Cow<'_, str>> for Argument { - fn from(value: &Cow<'_, str>) -> Self { - Self(value.clone().into()) +impl<'a> From<&Cow<'a, str>> for Argument<'a> { + fn from(value: &Cow<'a, str>) -> Self { + Self(value.clone()) } } -impl From for Argument { - fn from(value: String) -> Argument { - Self(value) +impl<'a> From for Argument<'a> { + fn from(value: String) -> Argument<'a> { + Self(Cow::Owned(value)) } } -impl From<&Path> for Argument { - fn from(value: &Path) -> Self { - Self::from(format!("{}", value.display())) - } +impl<'a> From<&'a Path> for Argument<'a> { + fn from(value: &'a Path) -> Self { Self::from(value.to_string_lossy()) } } -impl From for Argument { - fn from(value: PathBuf) -> Self { - Self::from(format!("{}", value.display())) - } +impl<'a> From for Argument<'a> { + fn from(value: PathBuf) -> Self { Self::from(format!("{}", value.display())) } } -impl From<&PathBuf> for Argument { +impl<'a> From<&PathBuf> for Argument<'a> { fn from(value: &PathBuf) -> Self { Self::from(format!("{}", value.display())) } } -impl From for Argument { - fn from(value: LanguageLevel) -> Self { - Self::from(value.as_ref().to_string()) - } +impl<'a> From for Argument<'a> { + fn from(value: LanguageLevel) -> Self { Self::from(value.as_ref().to_string()) } } -impl Borrow for Argument { +impl<'a> Borrow for Argument<'a> { fn borrow(&self) -> &str { &self.0 } } -impl AsRef for Argument { +impl<'a> AsRef for Argument<'a> { fn as_ref(&self) -> &OsStr { - OsStr::new(&self.0) + OsStr::new(self.0.as_ref()) } } -impl AsRef for Argument { +impl<'a> AsRef for Argument<'a> { fn as_ref(&self) -> &str { &self.0 } } -impl core::fmt::Display for Argument { +impl<'a> core::fmt::Display for Argument<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } @@ -103,9 +95,9 @@ impl core::fmt::Display for Argument { /// Strong type for represent a linear collection of [`Argument`] #[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct Arguments(Vec); +pub struct Arguments<'a>(Vec>); -impl core::fmt::Display for Arguments { +impl<'a> core::fmt::Display for Arguments<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.iter().try_for_each(|arg| write!(f, "{} ", arg)) // TODO: there's an ugly space at the end of every command line when Display is invoked @@ -113,9 +105,9 @@ impl core::fmt::Display for Arguments { } } -impl Arguments { +impl<'a> Arguments<'a> { /// Wraps an existing [`std::vec::Vec`] of [`Argument`] - pub fn from_vec(vec: Vec) -> Self { + pub fn from_vec(vec: Vec>) -> Self { Self(vec) } @@ -128,25 +120,25 @@ impl Arguments { /// from any type *T* that can be coerced into an [`Argument`] type pub fn push(&mut self, val: T) where - T: Into, + T: Into>, { self.0.push(val.into()) } /// Given an optional, adds the inner value if there's Some(<[Argument]>) - pub fn push_opt(&mut self, arg: Option) { + pub fn push_opt(&mut self, arg: Option>) { if let Some(val) = arg { self.0.push(val) } } /// Extends the underlying collection from an Iterator of [`Argument`] - pub fn extend(&mut self, iter: impl IntoIterator) { + pub fn extend(&mut self, iter: impl IntoIterator>) { self.0.extend(iter); } /// Extends the underlying collection given a slice of [`Argument`] - pub fn extend_from_slice(&mut self, slice: &[Argument]) { + pub fn extend_from_slice(&mut self, slice: &'a [Argument]) { self.0.extend_from_slice(slice); } @@ -160,16 +152,16 @@ impl Arguments { } } -impl Deref for Arguments { - type Target = [Argument]; +impl<'a> Deref for Arguments<'a> { + type Target = [Argument<'a>]; fn deref(&self) -> &Self::Target { &self.0 } } -impl IntoIterator for Arguments { - type Item = Argument; +impl<'a> IntoIterator for Arguments<'a> { + type Item = Argument<'a>; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { @@ -177,8 +169,8 @@ impl IntoIterator for Arguments { } } -impl IntoIterator for &Arguments { - type Item = Argument; +impl<'a> IntoIterator for &Arguments<'a> { + type Item = Argument<'a>; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { @@ -186,8 +178,8 @@ impl IntoIterator for &Arguments { } } -impl FromIterator for Arguments { - fn from_iter>(iter: I) -> Self { +impl<'a> FromIterator> for Arguments<'a> { + fn from_iter>>(iter: I) -> Self { let mut vec = Vec::new(); for item in iter { vec.push(item); @@ -196,8 +188,8 @@ impl FromIterator for Arguments { } } -impl<'a> FromIterator<&'a Argument> for Arguments { - fn from_iter>(iter: I) -> Arguments { +impl<'a> FromIterator<&'a Argument<'a>> for Arguments<'a> { + fn from_iter>>(iter: I) -> Arguments<'a> { let mut vec = Vec::new(); for item in iter { vec.push(item.clone()); @@ -277,10 +269,10 @@ pub mod msvc_args { use super::Arguments; - pub(crate) fn generate_std_cmd( - cache: &ZorkCache, + pub(crate) fn generate_std_cmd<'a>( + cache: &ZorkCache<'a>, stdlib_mode: StdLibMode, - ) -> SourceCommandLine { + ) -> SourceCommandLine<'a> { let mut arguments = Arguments::default(); let msvc = &cache.compilers_metadata.msvc; diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index 82a19978..fbd9f2fd 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -12,34 +12,64 @@ use std::{ use super::arguments::Argument; use crate::cache::EnvVars; use crate::cli::output::arguments::Arguments; -use crate::cli::output::commands::helpers::load_common_data; use crate::compiler::data_factory::{CommonArgs, CompilerCommonArguments}; use crate::domain::translation_unit::TranslationUnit; +use crate::utils::constants::error_messages; use crate::{ cache::ZorkCache, project_model::{compiler::CppCompiler, ZorkModel}, utils::constants, }; +use color_eyre::eyre::ContextCompat; use color_eyre::{ eyre::{eyre, Context}, Report, Result, }; use serde::{Deserialize, Serialize}; -pub fn run_generated_commands( - program_data: &ZorkModel<'_>, - cache: &mut ZorkCache<'_>, +pub fn run_generated_commands<'a>( + program_data: &ZorkModel<'a>, + cache: &mut ZorkCache<'a>, ) -> Result<()> { log::info!("Proceeding to execute the generated commands..."); - let (general_args, compiler_specific_shared_args, env_vars) = load_common_data(cache)?; - - let translation_units = cache - .generated_commands - .get_all_command_lines() + let generated_commands = &mut cache.generated_commands; + + let general_args = generated_commands + .general_args + .as_mut() + .expect(error_messages::GENERAL_ARGS_NOT_FOUND) + .get_args(); + + let compiler_specific_shared_args = generated_commands + .compiler_common_args + .as_mut() + .with_context(|| error_messages::COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND)? + .get_args(); + + let env_vars = match program_data.compiler.cpp_compiler { + CppCompiler::MSVC => &cache.compilers_metadata.msvc.env_vars, + CppCompiler::CLANG => &cache.compilers_metadata.clang.env_vars, + CppCompiler::GCC => &cache.compilers_metadata.gcc.env_vars, + }; + + let translation_units = generated_commands + .cpp_stdlib + .as_mut_slice() + .iter_mut() + .chain(generated_commands.c_compat_stdlib.as_mut_slice().iter_mut()) + .chain(generated_commands.system_modules.as_mut_slice().iter_mut()) + .chain(generated_commands.interfaces.as_mut_slice().iter_mut()) + .chain(generated_commands.implementations.as_mut_slice().iter_mut()) + .chain(generated_commands.sources.as_mut_slice().iter_mut()) .filter(|scl| scl.status.eq(&TranslationUnitStatus::PendingToBuild)) .collect::>(); + // let translation_units = generated_commands // TODO: how can I borrow twice generated_commands? + // .get_all_command_lines() + // .filter(|scl| scl.status.eq(&TranslationUnitStatus::PendingToBuild)) + // .collect::>(); + let compile_but_dont_link: [Argument; 1] = [Argument::from(match program_data.compiler.cpp_compiler { CppCompiler::CLANG | CppCompiler::GCC => "-c", @@ -55,7 +85,7 @@ pub fn run_generated_commands( .chain(translation_unit_cmd.args.iter()) .collect(); - let r = execute_command(program_data, &translation_unit_cmd_args, &env_vars); + let r = execute_command(program_data, &translation_unit_cmd_args, env_vars); translation_unit_cmd.status = TranslationUnitStatus::from(&r); if let Err(e) = r { @@ -77,15 +107,14 @@ pub fn run_generated_commands( .iter() .chain(compiler_specific_shared_args.iter()) .chain( - cache - .generated_commands + generated_commands .linker .get_target_output_for(program_data.compiler.cpp_compiler) .iter(), ) - .chain(cache.generated_commands.linker.byproducts.iter()) + .chain(generated_commands.linker.byproducts.iter()) .collect::(), - &env_vars, + env_vars, ); cache.generated_commands.linker.execution_result = TranslationUnitStatus::from(&r); @@ -157,17 +186,6 @@ where .with_context(|| format!("[{compiler}] - Command {arguments} failed!")) } -/// -pub trait CommandLine { - fn args(&self) -> &Arguments; -} - -impl CommandLine for SourceCommandLine { - fn args(&self) -> &Arguments { - &self.args - } -} - /// Type for representing the command line that will be sent to the target compiler, and /// store its different components /// @@ -178,15 +196,15 @@ impl CommandLine for SourceCommandLine { /// line can have among all the different iterations of the program, changing according to the modifications /// over the translation unit in the fs and the result of the build execution #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SourceCommandLine { +pub struct SourceCommandLine<'a> { pub directory: PathBuf, pub filename: String, - pub args: Arguments, + pub args: Arguments<'a>, pub status: TranslationUnitStatus, } -impl SourceCommandLine { - pub fn new<'a, T: TranslationUnit<'a>>(tu: &T, args: Arguments) -> Self { +impl<'a> SourceCommandLine<'a> { + pub fn new>(tu: &T, args: Arguments<'a>) -> Self { Self { directory: PathBuf::from(tu.parent()), filename: tu.filename(), @@ -205,14 +223,14 @@ impl SourceCommandLine { } #[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct LinkerCommandLine { - pub target: Argument, - pub byproducts: Arguments, - pub extra_args: Arguments, +pub struct LinkerCommandLine<'a> { + pub target: Argument<'a>, + pub byproducts: Arguments<'a>, + pub extra_args: Arguments<'a>, pub execution_result: TranslationUnitStatus, } -impl LinkerCommandLine { +impl<'a> LinkerCommandLine<'a> { pub fn get_target_output_for(&self, compiler: CppCompiler) -> Vec { match compiler { CppCompiler::CLANG | CppCompiler::GCC => { @@ -232,30 +250,30 @@ impl LinkerCommandLine { /// Holds the generated command line arguments for a concrete compiler #[derive(Serialize, Deserialize, Default, Debug)] -pub struct Commands { - pub cpp_stdlib: Option, - pub c_compat_stdlib: Option, +pub struct Commands<'a> { + pub cpp_stdlib: Option>, + pub c_compat_stdlib: Option>, // pub system_modules: HashMap, - pub system_modules: Vec, // TODO: SourceCommandLine while we found a better approach + pub system_modules: Vec>, // TODO: SourceCommandLine while we found a better approach // or while we don't implement the parser that gets the path to the compilers std library headers - pub general_args: Option, + pub general_args: Option>, pub compiler_common_args: Option>, - pub interfaces: Vec, - pub implementations: Vec, - pub sources: Vec, - pub linker: LinkerCommandLine, + pub interfaces: Vec>, + pub implementations: Vec>, + pub sources: Vec>, + pub linker: LinkerCommandLine<'a>, } -impl Commands { +impl<'a> Commands<'a> { /// Returns a [std::iter::Chain] (behind the opaque impl clause return type signature) - /// which points to all the generated commmands for the two variants of the compilers vendors C++ modular + /// which points to all the generated commands for the two variants of the compilers vendors C++ modular /// standard libraries implementations (see: [crate::project_model::compiler::StdLibMode]) /// joined to all the commands generated for every [TranslationUnit] declared by the user for /// its project pub fn get_all_command_lines( &mut self, - ) -> impl Iterator + Debug + '_ { + ) -> impl Iterator> + Debug { self.cpp_stdlib .as_mut_slice() .iter_mut() @@ -271,7 +289,7 @@ impl Commands { } } -impl core::fmt::Display for Commands { +impl<'a> core::fmt::Display for Commands<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, @@ -284,9 +302,9 @@ impl core::fmt::Display for Commands { } /// Convenient function to avoid code replication -fn collect_source_command_line( - iter: Iter<'_, SourceCommandLine>, // TODO: review this, for see if it's possible to consume the value and not cloning it -) -> impl Iterator + Debug + '_ { +fn collect_source_command_line<'a>( + iter: Iter<'a, SourceCommandLine>, // TODO: review this, for see if it's possible to consume the value and not cloning it +) -> impl Iterator + Debug + 'a { iter.map(|vec| { vec.args .iter() @@ -329,11 +347,10 @@ impl From<&Result> for TranslationUnitStatus { } mod helpers { - use crate::cache::{EnvVars, ZorkCache}; - use crate::cli::output::arguments::Arguments; + use crate::cli::output::commands::TranslationUnitStatus; - use crate::utils::constants::error_messages; - use color_eyre::eyre::{ContextCompat, Result}; + + use color_eyre::eyre::Result; use std::process::ExitStatus; /// Convenient way of handle a command execution result avoiding duplicate code @@ -351,26 +368,4 @@ mod helpers { Err(_) => TranslationUnitStatus::Error, } } - - pub(crate) fn load_common_data( - cache: &mut ZorkCache<'_>, - ) -> Result<(Arguments, Arguments, EnvVars)> { - let general_args = cache - .generated_commands - .general_args - .as_ref() - .expect(error_messages::GENERAL_ARGS_NOT_FOUND) - .get_args(); - - let compiler_specific_shared_args = cache - .generated_commands - .compiler_common_args - .as_ref() - .with_context(|| error_messages::COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND)? - .get_args(); - - let env_vars = cache.get_process_env_args().clone(); - - Ok((general_args, compiler_specific_shared_args, env_vars)) - } } diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index ef298955..9102a227 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -20,8 +20,8 @@ use crate::{ /// the ones held here are meant to be here because every supported compiler will use them, while the /// compiler args specific structs are holding the ones that are required depending on the compiler #[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct CommonArgs(Arguments); -impl CommonArgs { +pub struct CommonArgs<'a>(Arguments<'a>); +impl<'a> CommonArgs<'a> { pub fn get_args(&self) -> Arguments { self.0.clone() } @@ -31,7 +31,7 @@ impl CommonArgs { } } -impl<'a> From<&'a ZorkModel<'_>> for CommonArgs { +impl<'a> From<&'a ZorkModel<'_>> for CommonArgs<'a> { fn from(model: &'a ZorkModel<'_>) -> Self { let mut common_args = Arguments::default(); common_args.push(model.compiler.language_level_arg()); @@ -41,8 +41,8 @@ impl<'a> From<&'a ZorkModel<'_>> for CommonArgs { } } -impl IntoIterator for CommonArgs { - type Item = Argument; +impl<'a> IntoIterator for CommonArgs<'a> { + type Item = Argument<'a>; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 21ea1017..3736711c 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -36,35 +36,35 @@ pub mod data_factory; /// for every translation unit declared by the user for its project pub fn generate_commands<'a>( model: &'a ZorkModel<'a>, - mut cache: ZorkCache<'a>, + cache: &mut ZorkCache<'a>, cli_args: &'a CliArgs, -) -> Result> { +) -> Result<()> { // Load the general args and the compiler specific ones if it's necessary - load_flyweights_for_general_shared_data(model, &mut cache); + load_flyweights_for_general_shared_data(model, cache); // Build the std library as a module - generate_modular_stdlibs_cmds(model, &mut cache, cli_args); + generate_modular_stdlibs_cmds(model, cache); // Pre-tasks if model.compiler.cpp_compiler != CppCompiler::MSVC && !model.modules.sys_modules.is_empty() { - generate_sys_modules_commands(model, &mut cache, cli_args); + generate_sys_modules_commands(model, cache, cli_args); } // Translation units and linker // 1st - Build the modules - process_modules(model, &mut cache, cli_args)?; + process_modules(model, cache, cli_args)?; // 2nd - Generate the commands for the non-module sources - generate_sources_cmds_args(model, &mut cache, cli_args)?; + generate_sources_cmds_args(model, cache, cli_args)?; // 3rd - Generate the linker command for the 'target' declared by the user - generate_linkage_targets_commands(model, &mut cache, cli_args); + generate_linkage_targets_commands(model, cache, cli_args); - Ok(cache) + Ok(()) } /// Adds to the cache the data on the *flyweight* data structures that holds all the /// command line arguments that are shared among the command lines -fn load_flyweights_for_general_shared_data(model: &ZorkModel, cache: &mut ZorkCache) { +fn load_flyweights_for_general_shared_data<'a>(model: &'a ZorkModel, cache: &mut ZorkCache<'a>) { if cache.generated_commands.general_args.is_none() { cache.generated_commands.general_args = Some(CommonArgs::from(model)); } @@ -78,7 +78,7 @@ fn load_flyweights_for_general_shared_data(model: &ZorkModel, cache: &mut ZorkCa /// Generates the cmds for build the C++ standard libraries (std and std.compat) according to the specification /// of each compiler vendor -fn generate_modular_stdlibs_cmds(model: &ZorkModel, cache: &mut ZorkCache, _cli_args: &CliArgs) { +fn generate_modular_stdlibs_cmds<'a>(model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>) { // TODO: remaining ones: Clang, GCC. // NOTE: Provisionally 'If' guarded because only MSVC is supported now to build the // C++ standard library implementations @@ -177,7 +177,7 @@ fn generate_sources_cmds_args<'a>( /// for the files and properties declared for the tests section in the configuration file fn generate_linkage_targets_commands<'a>( model: &'a ZorkModel<'_>, - cache: &'a mut ZorkCache<'_>, + cache: &mut ZorkCache<'a>, cli_args: &'a CliArgs, ) { // TODO: Shouldn't we start to think about named targets? So introduce the static and dynamic @@ -197,7 +197,7 @@ fn generate_linkage_targets_commands<'a>( /// to clone them everytime we create a new [`SourceCommandLine`] for a given translation unit pub fn generate_linker_general_command_line_args<'a>( model: &ZorkModel<'_>, - cache: &mut ZorkCache<'_>, + cache: &mut ZorkCache<'a>, target: &'a impl ExecutableTarget<'a>, ) { log::info!("Generating the linker command line..."); @@ -236,21 +236,21 @@ pub fn generate_linker_general_command_line_args<'a>( /// (while the cache isn't purged by the user) to set their [`TranslationUnitStatus`] flag, which ultimately /// decides on every run if the file must be sent to build to the target [`CppCompiler`] fn process_kind_translation_units<'a, T: TranslationUnit<'a>>( - model: &ZorkModel<'_>, + model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, - cli_args: &CliArgs, + cli_args: &'a CliArgs, translation_units: &'a [T], for_kind: TranslationUnitKind, ) { - translation_units.iter().for_each(|translation_unit| { + for translation_unit in translation_units.iter() { process_kind_translation_unit(model, cache, cli_args, translation_unit, &for_kind) - }); + } } fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( - model: &ZorkModel<'_>, + model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, - cli_args: &CliArgs, + cli_args: &'a CliArgs, translation_unit: &'a T, for_kind: &TranslationUnitKind, ) { @@ -327,9 +327,9 @@ mod modules { /// Generates the expected arguments for precompile the BMIs depending on self pub fn generate_module_interface_cmd<'a>( - model: &'a ZorkModel, - cache: &'a mut ZorkCache, - interface: &'a ModuleInterfaceModel, + model: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + interface: &'a ModuleInterfaceModel<'a>, ) { let mut arguments = Arguments::default(); let compiler = model.compiler.cpp_compiler; @@ -398,9 +398,9 @@ mod modules { /// Generates the required arguments for compile the implementation module files pub fn generate_module_implementation_cmd<'a>( - model: &'a ZorkModel, - cache: &mut ZorkCache, - implementation: &'a ModuleImplementationModel, + model: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + implementation: &'a ModuleImplementationModel<'a>, ) { let compiler = model.compiler.cpp_compiler; let out_dir = model.build.output_dir.as_ref(); @@ -445,10 +445,10 @@ mod modules { /// /// This feature is supported by `GCC` and `Clang` /// NOTE: With the inclusion of std named modules, want we to support this anymore? - pub(crate) fn generate_sys_module_cmd( - model: &ZorkModel, - cache: &mut ZorkCache, - sys_module: &SystemModule, + pub(crate) fn generate_sys_module_cmd<'a>( + model: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + sys_module: &'a SystemModule<'a>, ) { let sys_module_name = &sys_module.file_stem; let generated_bmi_path = generate_bmi_file_path( @@ -486,9 +486,9 @@ mod modules { cache.generated_commands.system_modules.push(cmd); } - pub(crate) fn generate_modular_cpp_stdlib_args( - model: &ZorkModel, - cache: &mut ZorkCache, + pub(crate) fn generate_modular_cpp_stdlib_args<'a>( + model: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, stdlib_mode: StdLibMode, ) { let compiler = model.compiler.cpp_compiler; @@ -529,9 +529,9 @@ mod sources { /// Generates the command line arguments for non-module source files pub fn generate_sources_arguments<'a>( - model: &'a ZorkModel, - cache: &mut ZorkCache, - source: &'a SourceFile, + model: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + source: &'a SourceFile<'a>, target: &'a (impl ExecutableTarget<'a> + ?Sized), ) { let compiler = model.compiler.cpp_compiler; diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 157c922a..a2910678 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -103,8 +103,8 @@ pub mod worker { /// binaries, the tests declared for the projects... fn do_main_work_based_on_cli_input<'a>( cli_args: &'a CliArgs, - program_data: &'a ZorkModel<'_>, - cache: ZorkCache, + program_data: &'a ZorkModel<'a>, + mut cache: ZorkCache<'a>, ) -> Result<()> { // TODO: if we split the line below, we can only check for changes on the modified // files IF and only IF the configuration files has been modified @@ -113,7 +113,7 @@ pub mod worker { // // other is to have just a separate function that only passes the required data // like cache to be modified and the new ones - let mut cache = generate_commands(program_data, cache, cli_args) + generate_commands(program_data, &mut cache, cli_args) .with_context(|| error_messages::FAILURE_GENERATING_COMMANDS)?; let execution_result = match cli_args.command { diff --git a/zork++/src/lib/project_model/compiler.rs b/zork++/src/lib/project_model/compiler.rs index b47bdc49..06700b72 100644 --- a/zork++/src/lib/project_model/compiler.rs +++ b/zork++/src/lib/project_model/compiler.rs @@ -12,7 +12,7 @@ pub struct CompilerModel<'a> { pub driver_path: Cow<'a, str>, pub cpp_standard: LanguageLevel, pub std_lib: Option, - pub extra_args: Vec, + pub extra_args: Vec>, } impl<'a> CompilerModel<'a> { diff --git a/zork++/src/lib/project_model/executable.rs b/zork++/src/lib/project_model/executable.rs index c2e48f02..bbe80b10 100644 --- a/zork++/src/lib/project_model/executable.rs +++ b/zork++/src/lib/project_model/executable.rs @@ -10,7 +10,7 @@ use crate::{ pub struct ExecutableModel<'a> { pub executable_name: Cow<'a, str>, pub sourceset: SourceSet<'a>, - pub extra_args: Vec, + pub extra_args: Vec>, } impl<'a> ExtraArgs<'a> for ExecutableModel<'a> { diff --git a/zork++/src/lib/project_model/modules.rs b/zork++/src/lib/project_model/modules.rs index c542e8ef..cc751b82 100644 --- a/zork++/src/lib/project_model/modules.rs +++ b/zork++/src/lib/project_model/modules.rs @@ -17,7 +17,7 @@ pub struct ModulesModel<'a> { pub base_impls_dir: &'a Path, pub implementations: Vec>, pub sys_modules: Vec>, - pub extra_args: Vec, + pub extra_args: Vec>, } impl<'a> ExtraArgs<'a> for ModulesModel<'a> { diff --git a/zork++/src/lib/project_model/tests.rs b/zork++/src/lib/project_model/tests.rs index e0947bd1..4ce7bbb9 100644 --- a/zork++/src/lib/project_model/tests.rs +++ b/zork++/src/lib/project_model/tests.rs @@ -10,7 +10,7 @@ use super::sourceset::SourceSet; pub struct TestsModel<'a> { pub test_executable_name: Cow<'a, str>, pub sourceset: SourceSet<'a>, - pub extra_args: Vec, + pub extra_args: Vec>, } impl<'a> ExtraArgs<'a> for TestsModel<'a> { diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index c162b8a7..2958f9aa 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -353,7 +353,7 @@ fn assemble_module_implementation_model<'a>( fn assemble_tests_model<'a>( project_name: Cow<'_, str>, - config: Option, + config: Option>, project_root: &Path, ) -> TestsModel<'a> { let config = config.as_ref(); From 01a73a3cc12adbb3a5d6022f53560f126a783cdf Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 12 Jul 2024 17:25:36 +0200 Subject: [PATCH 33/73] fix: changed again the 'generate_commands' signature without reflecting it on the benchmarks module --- zork++/benches/benchmarks.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zork++/benches/benchmarks.rs b/zork++/benches/benchmarks.rs index 4de2a6d9..7d75f9b6 100644 --- a/zork++/benches/benchmarks.rs +++ b/zork++/benches/benchmarks.rs @@ -17,12 +17,13 @@ pub fn build_project_benchmark(c: &mut Criterion) { config_file::zork_cfg_from_file(utils::constants::CONFIG_FILE_MOCK).unwrap(); let cli_args = CliArgs::parse(); let program_data = build_model(config, &cli_args, Path::new(".")).unwrap(); - + let mut cache = ZorkCache::default(); + c.bench_function("Generate commands", |b| { b.iter(|| { generate_commands( black_box(&program_data), - black_box(ZorkCache::default()), + black_box(&mut cache), &cli_args, ) }) From 40ee054432f630bd014a76fb7643cfb46baadae9 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 14 Jul 2024 09:31:12 +0200 Subject: [PATCH 34/73] feat: Enabling GCC tests on Windows thanks to MSYS2 on the GitHub's virtual machines --- .../{tests-gcc.yml => tests-gcc-linux.yml} | 2 +- .github/workflows/tests-gcc-windows.yml | 69 +++++++++++++++++++ zork++/test/test.rs | 1 - 3 files changed, 70 insertions(+), 2 deletions(-) rename .github/workflows/{tests-gcc.yml => tests-gcc-linux.yml} (93%) create mode 100644 .github/workflows/tests-gcc-windows.yml diff --git a/.github/workflows/tests-gcc.yml b/.github/workflows/tests-gcc-linux.yml similarity index 93% rename from .github/workflows/tests-gcc.yml rename to .github/workflows/tests-gcc-linux.yml index 64d78d47..1adc4801 100644 --- a/.github/workflows/tests-gcc.yml +++ b/.github/workflows/tests-gcc-linux.yml @@ -8,7 +8,7 @@ on: jobs: tests: - name: Run the tests - GCC + name: Run the tests - GCC Linux runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/.github/workflows/tests-gcc-windows.yml b/.github/workflows/tests-gcc-windows.yml new file mode 100644 index 00000000..45d6028b --- /dev/null +++ b/.github/workflows/tests-gcc-windows.yml @@ -0,0 +1,69 @@ +name: GCC Tests For Windows MINGW based builds + +on: + push: + branches: 'main' + pull_request: + branches: '*' + +jobs: + tests: + name: Run the tests - GCC MinGW Windows + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + # - { icon: '⬛', sys: mingw32 } + - { icon: '🟦', sys: mingw64 } + # - { icon: '🟨', sys: ucrt64 } + # - { icon: '🟧', sys: clang64 } + name: ${{ matrix.icon }} ${{ matrix.sys }} + defaults: + run: + shell: msys2 {0} + steps: + + - name: '🧰 Checkout' + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: '${{ matrix.icon }} Setup MSYS2' + uses: msys2/setup-msys2@v2 + with: + msystem: ${{matrix.sys}} + cache: true + update: true + install: >- + git + pacboy: >- + toolchain:p + cmake:p + ninja:p + + - name: Cache 3rd party dependencies + id: cache-3rd-party-deps-restore + uses: actions/cache/restore@v3 + with: + path: | + install + key: ${{ runner.os }}-cache-3rd-party-deps + + + - name: Caching project dependencies + id: project-cache + uses: Swatinem/rust-cache@v2 + + - name: Running the tests for the project + run: | + cd zork++ + RUST_LOG=trace cargo test 2>&1 gcc_windows --color=always --no-fail-fast -- --nocapture --show-output --test-threads=1 + + - name: Cache 3rd party dependencies + id: cache-save-3rd-party-deps + uses: actions/cache/save@v3 + with: + path: | + install + key: ${{ steps.cache-3rd-party-deps-restore.outputs.cache-primary-key }} diff --git a/zork++/test/test.rs b/zork++/test/test.rs index c45b883a..ab36b4a0 100644 --- a/zork++/test/test.rs +++ b/zork++/test/test.rs @@ -114,7 +114,6 @@ fn test_msvc_full_process() -> Result<()> { #[cfg(target_os = "windows")] #[test] -#[ignore] // TODO: we need to add the `MSYS2` project on the Windows GitHub VM's fn test_gcc_windows_full_process() -> Result<()> { let project_name = "gcc_example"; From 2f8a9d45ecc5ac44ed665a0fa960e5fc6e00dde7 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 14 Jul 2024 09:36:51 +0200 Subject: [PATCH 35/73] chore: cargo fmt --- zork++/benches/benchmarks.rs | 10 ++-------- zork++/src/lib/cli/output/arguments.rs | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/zork++/benches/benchmarks.rs b/zork++/benches/benchmarks.rs index 7d75f9b6..aa8afa61 100644 --- a/zork++/benches/benchmarks.rs +++ b/zork++/benches/benchmarks.rs @@ -18,15 +18,9 @@ pub fn build_project_benchmark(c: &mut Criterion) { let cli_args = CliArgs::parse(); let program_data = build_model(config, &cli_args, Path::new(".")).unwrap(); let mut cache = ZorkCache::default(); - + c.bench_function("Generate commands", |b| { - b.iter(|| { - generate_commands( - black_box(&program_data), - black_box(&mut cache), - &cli_args, - ) - }) + b.iter(|| generate_commands(black_box(&program_data), black_box(&mut cache), &cli_args)) }); c.bench_function("Cache loading time", |b| { diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index 6f5c8b97..53ffba6a 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -36,7 +36,9 @@ impl<'a> From<&'a String> for Argument<'a> { } impl<'a> From> for Argument<'a> { - fn from(value: Cow<'a, str>) -> Self { Self(value) } + fn from(value: Cow<'a, str>) -> Self { + Self(value) + } } impl<'a> From<&Cow<'a, str>> for Argument<'a> { @@ -52,11 +54,15 @@ impl<'a> From for Argument<'a> { } impl<'a> From<&'a Path> for Argument<'a> { - fn from(value: &'a Path) -> Self { Self::from(value.to_string_lossy()) } + fn from(value: &'a Path) -> Self { + Self::from(value.to_string_lossy()) + } } impl<'a> From for Argument<'a> { - fn from(value: PathBuf) -> Self { Self::from(format!("{}", value.display())) } + fn from(value: PathBuf) -> Self { + Self::from(format!("{}", value.display())) + } } impl<'a> From<&PathBuf> for Argument<'a> { @@ -66,7 +72,9 @@ impl<'a> From<&PathBuf> for Argument<'a> { } impl<'a> From for Argument<'a> { - fn from(value: LanguageLevel) -> Self { Self::from(value.as_ref().to_string()) } + fn from(value: LanguageLevel) -> Self { + Self::from(value.as_ref().to_string()) + } } impl<'a> Borrow for Argument<'a> { From 2800fe7c0bf9daf43e03cbd95d8a7ea31683a681 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 14 Jul 2024 12:02:56 +0200 Subject: [PATCH 36/73] feat: Caching the project model, so it can be loaded as a cached entity instead of mapping the target cfg to the project model on every iteration. Closes #120 --- README.md | 54 +++++++++--------- zork++/src/lib/cache/mod.rs | 41 +++++++------- zork++/src/lib/compiler/mod.rs | 2 +- zork++/src/lib/config_file/compiler.rs | 2 +- zork++/src/lib/config_file/modules.rs | 4 -- zork++/src/lib/lib.rs | 65 ++++++++++++---------- zork++/src/lib/project_model/build.rs | 4 +- zork++/src/lib/project_model/compiler.rs | 2 +- zork++/src/lib/project_model/executable.rs | 4 +- zork++/src/lib/project_model/mod.rs | 60 ++++++++++++++++++-- zork++/src/lib/project_model/modules.rs | 24 +++----- zork++/src/lib/project_model/project.rs | 4 +- zork++/src/lib/project_model/sourceset.rs | 2 +- zork++/src/lib/project_model/tests.rs | 4 +- zork++/src/lib/utils/constants.rs | 2 + zork++/src/lib/utils/fs.rs | 10 ++-- zork++/src/lib/utils/reader.rs | 38 ++++++------- 17 files changed, 186 insertions(+), 136 deletions(-) diff --git a/README.md b/README.md index 2b752878..bc22fd4f 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ We recommend installing it in `/usr/bin`. ### macOS and another platforms -We currently don't provide installers or precompiled binaries for other operating systems. +We currently don't provide installers or precompiled binaries for other operating systems. You can build `Zork++` manually if your platform is a supported `Rust` target. You can check out [the list of available targets here](https://doc.rust-lang.org/nightly/rustc/platform-support.html). @@ -179,10 +179,10 @@ What happened here? See [The zork.toml config file](#zork_conf) section to have a better understanding on how to write the configuration file and your project. > [!NOTE] -> +> > This structure is just a guideline. You may prefer to organize your files in a completely different way. We are just providing a predefined layout, so you can quickly start working on your project. ->`Zork++` comes with this basic example by default, where is based on some driver code on the main file, a couple of module interfaces and module implementations. By passing `--template partitions` as a command line argument, you will have access to a more complex example, where module partitions and other module stuff appears, being a more sophisticated C++ modules project example. +>`Zork++` comes with this basic example by default, where is based on some driver code on the main file, a couple of module interfaces and module implementations. By passing `--template partitions` as a command line argument, you will have access to a more complex example, where module partitions and other module stuff appears, being a more sophisticated C++ modules project example. ## Let's explore the `out` directory a little @@ -242,8 +242,8 @@ sources = [ [modules] base_ifcs_dir = "./github-example/ifc" -interfaces = [ - { file = 'math.cppm' } +interfaces = [ + { file = 'math.cppm' } ] base_impls_dir = "./github-example/src" implementations = [ @@ -294,12 +294,12 @@ The optional `base_path` property allows you to specify a path where `Zork++` lo > Whenever you declare a module interface or a module implementation in the configuration file, you must take in consideration that sometimes modules (both interfaces or implementations) depend on other modules. Dependencies of one or more modules are declared as shown below: ```toml -interfaces = [ - { file = 'math.cppm' } +interfaces = [ + { file = 'math.cppm' } ] implementations = [ { file = 'math.cpp' }, # Direct mapping with the interface `math` - { file = 'math2.cpp', dependencies = ['math'] } + { file = 'math2.cpp', dependencies = ['math'] } # math2 isn't the same as math, so we need to specify the `math` dependency. ] ``` @@ -322,12 +322,12 @@ One thing that we haven't discussed are `module partitions`. As described by the ```toml [modules] -interfaces = [ +interfaces = [ { file = 'interface_partition.cppm', partition = { module = 'partitions' } }, { file = 'internal_partition.cpp', partition = { module = 'partitions', partition_name = 'internal_partition', is_internal_partition = true } }, { file = 'partitions.cppm' } ] -``` +``` *A closer look on how to work with module partitions within Zork++* We included `partitions` inside the `interfaces` key because, most of the time, other module interfaces will require some partition, and having a separate key for them will break the way of letting you decide in which order the translation units must be processed. @@ -342,7 +342,7 @@ Some peculiarities by compiler at the time of writing: This means that you aren't obligated to explicitly declare module names or module partition names... But, there's a specific case: `internal module partitions`. So, whenever you have an internal module partition, you must declare your translation unit as `partition`, and then provide at least `module` and `is_internal_partition` in order to make it work > [!NOTE] -> +> > In future releases, things about module partitions may change drastically (or not!). For example, we are expecting Clang to implement a good way of making implicit declarations but having the opportunity to specify a concrete output directory, among other things in other compilers too. ## The sys_modules property @@ -369,14 +369,14 @@ ZorkConfigFile { tests: Option, } -/// The [project] key +/// The [project] key ProjectAttribute { name: &'a str authors: Option>, compilation_db : bool } -/// The [compiler] key +/// The [compiler] key CompilerAttribute { cpp_compiler: CppCompiler, // clang, msvc or gcc driver_path: Option, // The invokable name for the compiler's binary @@ -385,7 +385,7 @@ CompilerAttribute { extra_args: Option> } -/// The [build] key +/// The [build] key BuildAttribute { output_dir: Option, } @@ -404,14 +404,12 @@ ExecutableAttribute { /// * `base_impls_dir` - Base directory. So you don't have to specify the full path of the implementation files /// * `implementations` - A list to define the module interface translation units for the project /// * `sys_modules` - An array field explicitly declare which system headers must be precompiled -/// * `extra_args` - Extra arguments that will be added to the generated command lines ModulesAttribute { base_ifcs_dir: Option, interfaces: Option>, base_impls_dir: Option, implementations: Option>, sys_modules: Option>, - extra_args: Option>, } /// The [tests] key @@ -420,7 +418,7 @@ TestsAttribute { sources_base_path: Option, sources: Option>, extra_args: Option>, -} +} ``` ## A closer look on the `ModulesAttribute` key @@ -429,19 +427,19 @@ TestsAttribute { /// [`ModuleInterface`] - A module interface structure for dealing /// with the parse work of prebuilt module interface units /// -/// * `file`- The path of a primary module interface +/// * `file`- The path of a primary module interface /// (relative to base_ifcs_path if applies) /// /// * `module_name` - An optional field for make an explicit -/// declaration of the C++ module on this module interface +/// declaration of the C++ module on this module interface /// with the `export module 'module_name' statement. /// If this attribute isn't present, Zork++ will assume that the -/// C++ module declared within this file is equals +/// C++ module declared within this file is equals /// to the filename /// -/// * `partition` - Whenever this attribute is present, +/// * `partition` - Whenever this attribute is present, /// we are telling Zork++ that the actual translation unit -/// is a partition, either an interface partition +/// is a partition, either an interface partition /// or an implementation partition unit /// /// * `dependencies` - An optional array field for declare the module interfaces @@ -460,11 +458,11 @@ ModuleInterface { /// /// * `partition_name` - An optional field for explicitly declare the name of a module interface /// partition, or a module implementation partition. -/// Currently this requirement is only needed if your partitions +/// Currently this requirement is only needed if your partitions /// file names aren't declared as the modules convention, /// that is `module_name-partition_name.extension` /// -/// * `is_internal_partition` - Optional field for declare that +/// * `is_internal_partition` - Optional field for declare that /// the module is actually a module for hold implementation /// details, known as module implementation partitions. /// This option only takes effect with MSVC @@ -557,15 +555,15 @@ But this is not available in every compiler using `C++20`, and at the time of wr In `Zork++`, you have this feature enabled if: - You're working with `Clang` because the `modulemap` feature of `Clang`. So, in your project, you're able to: - + - `import std;` This our preferred way, in line with the C++23 feature. Under *Windows*, this is made automatically, because we manually generate a `module.modulemap` file that takes care to include the need system headers under the `import std;` statement. In *Unix* kind of operating systems, this is automatically passed as a requirement to `Clang` with a requirement. `libc++` must be installed in your machine. If there's no `libc++` or `libc++-dev` library installed in your computer, you will see some error like: `import std; --> Error, module not found` So, make sure that you installed the `Clang's` implementation of the *standard library* to take advantage of this feature. On *Debian* based systems, you can just use `$ sudo apt install libc++-dev`. On *Arch* systems, just `$ sudo pacman -Sy libc++`. > In any case, make sure that you enabled *libc++* as your standard library in your **zork.toml** configuration file. - - - As alternative, you can use `import ;` This is, individually import some specific system header as a module. + + - As alternative, you can use `import ;` This is, individually import some specific system header as a module. Needs an explicit pre-compilation process. This is supported by `Clang` and `GCC` (since we are not able to do an `import std` for `GCC` builds). - + - You're working with `MSVC`, you are able to use `import std.core`, as a compiler specific feature. But this will allow you to use import statements instead of `#include` directives. In upcoming releases will we adapt to the real way on how Microsoft's compiler deals with this feature, so `Zork++` users will be able to correctly use `import std;` in their codebases with *MSVC*, not the workarounds existing up until this point. diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index ca6e50ba..c7ebe9c6 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -14,6 +14,7 @@ use std::{ path::{Path, PathBuf}, }; +use crate::config_file::ZorkConfigFile; use crate::domain::translation_unit::{TranslationUnit, TranslationUnitKind}; use crate::project_model::sourceset::SourceFile; use crate::utils::constants::CACHE_FILE_EXT; @@ -31,9 +32,17 @@ use crate::project_model::compiler::StdLibMode; /// Standalone utility for load from the file system the Zork++ cache file /// for the target [`CppCompiler`] -pub fn load<'a>(program_data: &'a ZorkModel<'_>, cli_args: &CliArgs) -> Result> { - let compiler = program_data.compiler.cpp_compiler; - let cache_path = &program_data.build.output_dir.join("zork").join("cache"); +pub fn load<'a>(config: &ZorkConfigFile<'a>, cli_args: &CliArgs) -> Result> { + let compiler: CppCompiler = config.compiler.cpp_compiler.into(); + let cache_path = Path::new( + &config + .build + .as_ref() + .and_then(|build_attr| build_attr.output_dir) + .unwrap_or("out"), + ) + .join("zork") + .join("cache"); let cache_file_path = cache_path .join(compiler.as_ref()) @@ -42,16 +51,16 @@ pub fn load<'a>(program_data: &'a ZorkModel<'_>, cli_args: &CliArgs) -> Result__.json or similar? // Or just ...//_.json - let mut cache = if !Path::new(&cache_file_path).exists() { + let cache = if !cache_file_path.exists() { File::create(&cache_file_path).with_context(|| "Error creating the cache file")?; - helpers::initialize_default_cache(compiler, cache_file_path)? - } else if Path::new(cache_path).exists() && cli_args.clear_cache { - fs::remove_dir_all(cache_path).with_context(|| "Error cleaning the Zork++ cache")?; + helpers::initialize_default_cache(cache_file_path)? + } else if cache_path.exists() && cli_args.clear_cache { + fs::remove_dir_all(&cache_path).with_context(|| "Error cleaning the Zork++ cache")?; fs::create_dir(cache_path) .with_context(|| "Error creating the cache subdirectory for {compiler}")?; File::create(&cache_file_path) .with_context(|| "Error creating the cache file after cleaning the cache")?; - helpers::initialize_default_cache(compiler, cache_file_path)? + helpers::initialize_default_cache(cache_file_path)? } else { log::trace!( "Loading Zork++ cache file for {compiler} at: {:?}", @@ -61,16 +70,11 @@ pub fn load<'a>(program_data: &'a ZorkModel<'_>, cli_args: &CliArgs) -> Result { - pub compiler: CppCompiler, pub compilers_metadata: CompilersMetadata<'a>, pub generated_commands: Commands<'a>, pub metadata: CacheMetadata, @@ -195,8 +199,8 @@ impl<'a> ZorkCache<'a> { /// Method that returns the HashMap that holds the environmental variables that must be passed /// to the underlying shell - pub fn get_process_env_args(&'a mut self) -> &'a EnvVars { - match self.compiler { + pub fn get_process_env_args(&'a mut self, compiler: CppCompiler) -> &'a EnvVars { + match compiler { CppCompiler::MSVC => &self.compilers_metadata.msvc.env_vars, CppCompiler::CLANG => &self.compilers_metadata.clang.env_vars, CppCompiler::GCC => &self.compilers_metadata.gcc.env_vars, @@ -398,12 +402,8 @@ mod helpers { use super::*; use std::path::PathBuf; - pub(crate) fn initialize_default_cache<'a>( - compiler: CppCompiler, - cache_file_path: PathBuf, - ) -> Result> { + pub(crate) fn initialize_default_cache<'a>(cache_file_path: PathBuf) -> Result> { let default_initialized = ZorkCache { - compiler, metadata: CacheMetadata { cache_file_path: cache_file_path.clone(), ..Default::default() @@ -413,6 +413,7 @@ mod helpers { utils::fs::serialize_object_to_file(&cache_file_path, &default_initialized) .with_context(move || "Error saving data to the Zork++ cache")?; + Ok(default_initialized) } } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 3736711c..f2de2225 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -254,7 +254,7 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( translation_unit: &'a T, for_kind: &TranslationUnitKind, ) { - let compiler = cache.compiler; + let compiler = model.compiler.cpp_compiler; let lpe = cache.metadata.last_program_execution; if let Some(generated_cmd) = cache.get_cmd_for_translation_unit_kind(translation_unit, for_kind) diff --git a/zork++/src/lib/config_file/compiler.rs b/zork++/src/lib/config_file/compiler.rs index 6e790a47..392cd1d9 100644 --- a/zork++/src/lib/config_file/compiler.rs +++ b/zork++/src/lib/config_file/compiler.rs @@ -82,7 +82,7 @@ pub struct CompilerAttribute<'a> { } /// The C++ compilers available within Zork++ -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Default)] pub enum CppCompiler { #[serde(alias = "CLANG", alias = "Clang", alias = "clang")] #[default] diff --git a/zork++/src/lib/config_file/modules.rs b/zork++/src/lib/config_file/modules.rs index 3c8af6fd..a8df0038 100644 --- a/zork++/src/lib/config_file/modules.rs +++ b/zork++/src/lib/config_file/modules.rs @@ -9,7 +9,6 @@ use serde::{Deserialize, Serialize}; /// * `implementations` - A list to define the module interface translation units for the project /// * `sys_modules` - An array field explicitly declare which system headers /// must be precompiled in order to make the importable translation units -/// * `extra_args` - Extra arguments that will be added to the generated command lines /// /// ### Tests /// @@ -25,7 +24,6 @@ use serde::{Deserialize, Serialize}; /// { file = 'math.cpp' }, { file = 'some_module_impl.cpp', dependencies = ['iostream'] } /// ] /// sys_modules = ['iostream', 'vector', 'string', 'type_traits', 'functional'] -/// extra_args = ['-Wall'] /// "#; /// /// let config: ModulesAttribute = toml::from_str(CONFIG_FILE_MOCK) @@ -72,8 +70,6 @@ pub struct ModulesAttribute<'a> { pub implementations: Option>>, #[serde(borrow)] pub sys_modules: Option>, - #[serde(borrow)] - pub extra_args: Option>, } /// [`ModuleInterface`] - A module interface structure for dealing diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index a2910678..a59201f4 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -15,10 +15,12 @@ pub mod utils; /// without having to do fancy work about checking the /// data sent to stdout/stderr pub mod worker { + use crate::config_file::ZorkConfigFile; + use crate::project_model; use crate::{config_file, utils::fs::get_project_root_absolute_path}; use std::{fs, path::Path}; - use crate::utils::constants::{dir_names, error_messages}; + use crate::utils::constants::{dir_names, error_messages, ZORK}; use crate::{ cache::{self, ZorkCache}, cli::{ @@ -29,7 +31,7 @@ pub mod worker { project_model::{compiler::CppCompiler, ZorkModel}, utils::{ self, - reader::{build_model, find_config_files, ConfigFile}, + reader::{find_config_files, ConfigFile}, template::create_templated_project, }, }; @@ -52,7 +54,7 @@ pub mod worker { template, } = cli_args.command { - // TODO pass here the driver's path? so it's already configured on the autogenerated + // TODO: pass here the driver's path? so it's already configured on the autogenerated // zork.toml file? return create_templated_project( &abs_project_root, @@ -65,30 +67,31 @@ pub mod worker { let config_files: Vec = find_config_files(project_root, &cli_args.match_files) .with_context(|| "We didn't found a valid Zork++ configuration file")?; - log::trace!("Config files found: {config_files:?}"); - // TODO: add the last modified time of the cfg file for config_file in config_files { - let cfg_fn = config_file.dir_entry.file_name(); + let cfg_path = &config_file.path; log::debug!( "Launching a Zork++ work event for the configuration file: {:?}", - cfg_fn, + cfg_path, ); - let raw_file = fs::read_to_string(config_file.path) - .with_context(|| format!("{}: {:?}", error_messages::READ_CFG_FILE, cfg_fn))?; + let raw_file = fs::read_to_string(cfg_path) + .with_context(|| format!("{}: {:?}", error_messages::READ_CFG_FILE, cfg_path))?; let config = config_file::zork_cfg_from_file(raw_file.as_str()) .with_context(|| error_messages::PARSE_CFG_FILE)?; - let program_data = build_model(config, cli_args, &abs_project_root)?; - create_output_directory(&program_data)?; // TODO: avoid this call without check if exists - let cache = cache::load(&program_data, cli_args)?; + create_output_directory(&config)?; // TODO: avoid this call without check if exists + let cache = cache::load(&config, cli_args)?; + // TODO: Big one, need to call cache.load_tasks or whatever, or metadata won't be + // loaded + + let program_data = project_model::load(config, cli_args, &abs_project_root)?; do_main_work_based_on_cli_input(cli_args, &program_data, cache).with_context(|| { format!( "{}: {:?}", error_messages::FAILED_BUILD_FOR_CFG_FILE, - config_file.dir_entry.file_name() + cfg_path ) })?; } @@ -152,18 +155,24 @@ pub mod worker { /// - a /cache folder, where lives the metadata cached by Zork++ /// in order to track different aspects of the program (last time /// modified files, last process build time...) - fn create_output_directory(model: &ZorkModel) -> Result<()> { - let out_dir = &model.build.output_dir; - let compiler: &str = model.compiler.cpp_compiler.as_ref(); + fn create_output_directory(config: &ZorkConfigFile) -> Result<()> { + let compiler: CppCompiler = config.compiler.cpp_compiler.into(); + let compiler_name = compiler.as_ref(); + let binding = config + .build + .as_ref() + .and_then(|build_attr| build_attr.output_dir) + .unwrap_or("out"); + let out_dir = Path::new(&binding); // Recursively create the directories below and all of its parent components if they are missing - let modules_path = out_dir.join(compiler).join("modules"); + let modules_path = out_dir.join(compiler_name).join(dir_names::MODULES); - let zork_path = out_dir.join("zork"); + let zork_path = out_dir.join(ZORK); let zork_cache_path = zork_path.join(dir_names::CACHE); let zork_intrinsics_path = zork_path.join(dir_names::INTRINSICS); - utils::fs::create_directory(&out_dir.join(compiler).join(dir_names::OBJECT_FILES))?; + utils::fs::create_directory(&out_dir.join(compiler_name).join(dir_names::OBJECT_FILES))?; utils::fs::create_directory(&modules_path.join(dir_names::INTERFACES))?; utils::fs::create_directory(&modules_path.join(dir_names::IMPLEMENTATIONS))?; @@ -173,7 +182,7 @@ pub mod worker { utils::fs::create_directory(&zork_intrinsics_path)?; // TODO: This possibly gonna be temporary - if model.compiler.cpp_compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { + if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { utils::fs::create_file( &zork_intrinsics_path, "std.h", @@ -192,14 +201,13 @@ pub mod worker { #[cfg(test)] mod tests { - use crate::cli::input::CliArgs; - use clap::Parser; - use color_eyre::{eyre::Context, Result}; + use crate::project_model::compiler::CppCompiler; + use crate::utils::template::resources::CONFIG_FILE; + use color_eyre::Result; use tempfile::tempdir; use crate::config_file::{self, ZorkConfigFile}; - use crate::utils::constants::{dir_names, error_messages, ZORK}; - use crate::utils::{reader::build_model, template::resources::CONFIG_FILE}; + use crate::utils::constants::{dir_names, ZORK}; #[test] fn test_creation_directories() -> Result<()> { @@ -214,16 +222,13 @@ pub mod worker { .replace("", "LIBCPP") .replace('\\', "/"); let zcf: ZorkConfigFile = config_file::zork_cfg_from_file(&normalized_cfg_file)?; - let cli_args = CliArgs::parse_from(["", "-vv", "run"]); - let model = build_model(zcf, &cli_args, temp_path) - .with_context(|| error_messages::PROJECT_MODEL_MAPPING)?; - let compiler = model.compiler.cpp_compiler; + let compiler: CppCompiler = zcf.compiler.cpp_compiler.into(); let compiler_folder_dir = out_dir.join(compiler.as_ref()); let modules_path = compiler_folder_dir.join("modules"); // This should create and out/ directory at the root of the tmp path - super::create_output_directory(&model)?; + super::create_output_directory(&zcf)?; assert!(out_dir.exists()); diff --git a/zork++/src/lib/project_model/build.rs b/zork++/src/lib/project_model/build.rs index 564574bc..8737be62 100644 --- a/zork++/src/lib/project_model/build.rs +++ b/zork++/src/lib/project_model/build.rs @@ -1,6 +1,8 @@ use std::path::PathBuf; -#[derive(Debug, PartialEq, Eq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct BuildModel { pub output_dir: PathBuf, } diff --git a/zork++/src/lib/project_model/compiler.rs b/zork++/src/lib/project_model/compiler.rs index 06700b72..eaac2a05 100644 --- a/zork++/src/lib/project_model/compiler.rs +++ b/zork++/src/lib/project_model/compiler.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::domain::target::ExtraArgs; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct CompilerModel<'a> { pub cpp_compiler: CppCompiler, pub driver_path: Cow<'a, str>, diff --git a/zork++/src/lib/project_model/executable.rs b/zork++/src/lib/project_model/executable.rs index bbe80b10..2d3f1793 100644 --- a/zork++/src/lib/project_model/executable.rs +++ b/zork++/src/lib/project_model/executable.rs @@ -1,12 +1,14 @@ use std::borrow::Cow; +use serde::{Deserialize, Serialize}; + use super::sourceset::SourceSet; use crate::{ cli::output::arguments::Argument, domain::target::{ExecutableTarget, ExtraArgs}, }; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct ExecutableModel<'a> { pub executable_name: Cow<'a, str>, pub sourceset: SourceSet<'a>, diff --git a/zork++/src/lib/project_model/mod.rs b/zork++/src/lib/project_model/mod.rs index 467e9046..5dca2dde 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -6,14 +6,32 @@ pub mod project; pub mod sourceset; pub mod tests; -use std::fmt::Debug; +use std::{fmt::Debug, path::Path}; + +use color_eyre::eyre::Context; +use color_eyre::Result; +use serde::{Deserialize, Serialize}; + +use crate::{ + cli::input::CliArgs, + config_file::ZorkConfigFile, + utils::{ + self, + constants::{error_messages, CACHE_FILE_EXT}, + reader, + }, +}; use self::{ - build::BuildModel, compiler::CompilerModel, executable::ExecutableModel, modules::ModulesModel, - project::ProjectModel, tests::TestsModel, + build::BuildModel, + compiler::{CompilerModel, CppCompiler}, + executable::ExecutableModel, + modules::ModulesModel, + project::ProjectModel, + tests::TestsModel, }; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct ZorkModel<'a> { pub project: ProjectModel<'a>, pub compiler: CompilerModel<'a>, @@ -22,3 +40,37 @@ pub struct ZorkModel<'a> { pub modules: ModulesModel<'a>, pub tests: TestsModel<'a>, } + +/// Loads the mapped [`ZorkModel`] for a concrete [`ZorkConfigFile`] if a save file exists, +/// otherwise, calls the mapping processor to load the data from the configuration file +pub fn load<'a>( + config: ZorkConfigFile<'a>, + cli_args: &'a CliArgs, + absolute_project_root: &Path, +) -> Result> { + let compiler: CppCompiler = config.compiler.cpp_compiler.into(); + let cache_path = Path::new( + &config + .build + .as_ref() + .and_then(|build_attr| build_attr.output_dir) + .unwrap_or("out"), + ) + .join("zork") + .join("cache"); + + let cached_project_model_path = cache_path + .join(format!("{}_pm", compiler.as_ref())) + .with_extension(CACHE_FILE_EXT); + + utils::fs::load_and_deserialize::(&cached_project_model_path) + .or_else(|_| { + log::debug!("Proceding to map the configuration file to the ZorkModel entity, since no cached project model was found"); + let program_data: ZorkModel = reader::build_model(config, cli_args, absolute_project_root)?; + utils::fs::serialize_object_to_file::(&cached_project_model_path, &program_data) + .with_context(|| error_messages::PROJECT_MODEL_SAVE)?; + + Ok::, color_eyre::eyre::Error>(program_data) + }) + .with_context(|| "Error loading the project model") +} diff --git a/zork++/src/lib/project_model/modules.rs b/zork++/src/lib/project_model/modules.rs index cc751b82..a58bfb70 100644 --- a/zork++/src/lib/project_model/modules.rs +++ b/zork++/src/lib/project_model/modules.rs @@ -2,31 +2,23 @@ use core::fmt; use std::borrow::Cow; use std::path::{Path, PathBuf}; +use serde::{Deserialize, Serialize}; use transient::Transient; -use crate::cli::output::arguments::Argument; use crate::config_file::modules::ModulePartition; -use crate::domain::target::ExtraArgs; use crate::domain::translation_unit::TranslationUnit; use crate::impl_translation_unit_for; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct ModulesModel<'a> { - pub base_ifcs_dir: &'a Path, + pub base_ifcs_dir: Cow<'a, Path>, pub interfaces: Vec>, - pub base_impls_dir: &'a Path, + pub base_impls_dir: Cow<'a, Path>, pub implementations: Vec>, pub sys_modules: Vec>, - pub extra_args: Vec>, } -impl<'a> ExtraArgs<'a> for ModulesModel<'a> { - fn extra_args(&'a self) -> &'a [Argument] { - &self.extra_args - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Transient)] +#[derive(Debug, PartialEq, Eq, Clone, Transient, Serialize, Deserialize, Default)] pub struct ModuleInterfaceModel<'a> { pub path: PathBuf, pub file_stem: Cow<'a, str>, @@ -51,7 +43,7 @@ impl<'a> fmt::Display for ModuleInterfaceModel<'a> { } } -#[derive(Debug, PartialEq, Eq, Transient, Clone)] +#[derive(Debug, PartialEq, Eq, Transient, Clone, Serialize, Deserialize, Default)] pub struct ModulePartitionModel<'a> { pub module: Cow<'a, str>, pub partition_name: Cow<'a, str>, @@ -68,7 +60,7 @@ impl<'a> From> for ModulePartitionModel<'a> { } } -#[derive(Debug, PartialEq, Eq, Transient)] +#[derive(Debug, PartialEq, Eq, Transient, Serialize, Deserialize, Default)] pub struct ModuleImplementationModel<'a> { pub path: PathBuf, pub file_stem: Cow<'a, str>, @@ -87,7 +79,7 @@ impl<'a> fmt::Display for ModuleImplementationModel<'a> { /// Holds the fs information about the `C++` system headers, which they can be built as /// binary module interface for certain compilers, while allowing to import those system headers /// as modules -#[derive(Debug, PartialEq, Eq, Default, Transient)] +#[derive(Debug, PartialEq, Eq, Transient, Serialize, Deserialize, Default)] pub struct SystemModule<'a> { pub path: PathBuf, pub file_stem: Cow<'a, str>, diff --git a/zork++/src/lib/project_model/project.rs b/zork++/src/lib/project_model/project.rs index e7905cfa..b96d37a1 100644 --- a/zork++/src/lib/project_model/project.rs +++ b/zork++/src/lib/project_model/project.rs @@ -1,6 +1,8 @@ use std::borrow::Cow; -#[derive(Debug, PartialEq, Eq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct ProjectModel<'a> { pub name: Cow<'a, str>, pub authors: Vec>, diff --git a/zork++/src/lib/project_model/sourceset.rs b/zork++/src/lib/project_model/sourceset.rs index 3b066bce..20cd5f27 100644 --- a/zork++/src/lib/project_model/sourceset.rs +++ b/zork++/src/lib/project_model/sourceset.rs @@ -57,7 +57,7 @@ impl GlobPattern { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct SourceSet<'a> { pub sources: Vec>, } diff --git a/zork++/src/lib/project_model/tests.rs b/zork++/src/lib/project_model/tests.rs index 4ce7bbb9..48be167a 100644 --- a/zork++/src/lib/project_model/tests.rs +++ b/zork++/src/lib/project_model/tests.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + use crate::{ cli::output::arguments::Argument, domain::target::{ExecutableTarget, ExtraArgs}, @@ -6,7 +8,7 @@ use std::borrow::Cow; use super::sourceset::SourceSet; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct TestsModel<'a> { pub test_executable_name: Cow<'a, str>, pub sourceset: SourceSet<'a>, diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index c9d737cf..169212c2 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -7,6 +7,7 @@ pub mod dir_names { pub const DEFAULT_OUTPUT_DIR: &str = "out"; pub const CACHE: &str = "cache"; pub const STD: &str = "std"; + pub const MODULES: &str = "modules"; pub const INTRINSICS: &str = "intrinsics"; pub const INTERFACES: &str = "interfaces"; pub const IMPLEMENTATIONS: &str = "implementations"; @@ -21,6 +22,7 @@ pub mod error_messages { pub const FAILED_BUILD_FOR_CFG_FILE: &str = "Failed to build the project for the config file"; pub const GENERAL_ARGS_NOT_FOUND: &str = "Something went wrong loading the general arguments"; pub const PROJECT_MODEL_MAPPING: &str = "Error building the project model"; + pub const PROJECT_MODEL_SAVE: &str = "Error caching and saving to the fs the project model"; pub const COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND: &str = "Something went wrong loading the general arguments"; pub const CLI_ARGS_CMD_NEW_BRANCH: &str = diff --git a/zork++/src/lib/utils/fs.rs b/zork++/src/lib/utils/fs.rs index 2ff3d325..067b1ebf 100644 --- a/zork++/src/lib/utils/fs.rs +++ b/zork++/src/lib/utils/fs.rs @@ -97,9 +97,11 @@ where pub fn load_and_deserialize(path: &P) -> Result where T: for<'a> Deserialize<'a> + Default, - P: AsRef, + P: AsRef + std::fmt::Debug, { - let buffer = - BufReader::new(File::open(path.as_ref()).with_context(|| "Error opening the cache file")?); - Ok(serde_json::from_reader(buffer).expect("Unable to parse the Zork++ cache file")) + let buffer = BufReader::new( + File::open(path.as_ref()).with_context(|| format!("Error opening {:?}", path))?, + ); + Ok(serde_json::from_reader(buffer) + .unwrap_or_else(|_| panic!("Unable to parse file: {:?}", path))) } diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 2958f9aa..367fc268 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -27,10 +27,11 @@ use crate::{ }, utils, }; +use chrono::{DateTime, Utc}; use color_eyre::{eyre::eyre, Result}; use std::borrow::Cow; use std::path::{Path, PathBuf}; -use walkdir::{DirEntry, WalkDir}; +use walkdir::WalkDir; use super::constants::dir_names; @@ -40,8 +41,8 @@ use super::constants::dir_names; /// at a valid path in some subdirectory #[derive(Debug)] pub struct ConfigFile { - pub dir_entry: DirEntry, pub path: PathBuf, + pub last_time_modified: DateTime, } /// Checks for the existence of the `zork_.toml` configuration files @@ -64,7 +65,7 @@ pub fn find_config_files( let mut files = vec![]; for e in WalkDir::new(base_path) - .max_depth(2) + .max_depth(2) // TODO: so, max_depth should be zero when the cfg arg is ready .into_iter() .filter_map(|e| e.ok()) { @@ -79,8 +80,8 @@ pub fn find_config_files( && filename.contains(file_match) { files.push(ConfigFile { - dir_entry: e.clone(), path: e.path().to_path_buf(), + last_time_modified: DateTime::::from(e.metadata()?.modified()?), }) } } @@ -151,7 +152,7 @@ fn assemble_compiler_model<'a>( .unwrap_or_default(); CompilerModel { - cpp_compiler: config.cpp_compiler.clone().into(), + cpp_compiler: config.cpp_compiler.into(), driver_path: if let Some(driver_path) = cli_args.driver_path.as_ref() { Cow::Borrowed(driver_path) } else { @@ -220,14 +221,15 @@ fn assemble_modules_model<'a>( let base_ifcs_dir = modules .base_ifcs_dir .map(Path::new) - .unwrap_or_else(|| Path::new(".")); + .map(Cow::from) + .unwrap_or_default(); let interfaces = modules .interfaces .map(|ifcs| { ifcs.into_iter() .map(|m_ifc| -> ModuleInterfaceModel<'_> { - assemble_module_interface_model(m_ifc, base_ifcs_dir, project_root) + assemble_module_interface_model(m_ifc, &base_ifcs_dir, project_root) }) .collect() }) @@ -236,7 +238,8 @@ fn assemble_modules_model<'a>( let base_impls_dir = modules .base_impls_dir .map(Path::new) - .unwrap_or_else(|| Path::new(".")); + .map(Cow::from) + .unwrap_or_default(); let implementations = modules .implementations @@ -244,7 +247,7 @@ fn assemble_modules_model<'a>( impls .into_iter() .map(|m_impl| { - assemble_module_implementation_model(m_impl, base_impls_dir, project_root) + assemble_module_implementation_model(m_impl, &base_impls_dir, project_root) }) .collect() }) @@ -263,19 +266,12 @@ fn assemble_modules_model<'a>( .collect() }); - let extra_args = modules // TODO: this has to disappear from the Zork++ build options - .extra_args - .as_ref() - .map(|args| args.iter().map(|arg| Argument::from(*arg)).collect()) - .unwrap_or_default(); - ModulesModel { base_ifcs_dir, interfaces, base_impls_dir, implementations, sys_modules, - extra_args, } } @@ -470,12 +466,11 @@ mod test { extra_args: vec![], }, modules: ModulesModel { - base_ifcs_dir: Path::new("."), + base_ifcs_dir: Cow::default(), interfaces: vec![], - base_impls_dir: Path::new("."), + base_impls_dir: Cow::default(), implementations: vec![], sys_modules: vec![], - extra_args: vec![], }, tests: TestsModel { test_executable_name: "Zork++_test".into(), @@ -520,7 +515,7 @@ mod test { extra_args: vec![Argument::from("-Werr")], }, modules: ModulesModel { - base_ifcs_dir: Path::new("ifcs"), + base_ifcs_dir: Cow::Borrowed(Path::new("ifcs")), interfaces: vec![ ModuleInterfaceModel { path: abs_path_for_mock.join("ifcs"), @@ -539,7 +534,7 @@ mod test { dependencies: vec![], }, ], - base_impls_dir: Path::new("srcs"), + base_impls_dir: Cow::Borrowed(Path::new("srcs")), implementations: vec![ ModuleImplementationModel { path: abs_path_for_mock.join("srcs"), @@ -558,7 +553,6 @@ mod test { file_stem: Cow::Borrowed("iostream"), ..Default::default() }], - extra_args: vec![Argument::from("-Wall")], }, tests: TestsModel { test_executable_name: "zork_check".into(), From e9de9363039024d12af86cbed51dff131a50c647 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 14 Jul 2024 12:35:06 +0200 Subject: [PATCH 37/73] fix: missing the project root on the cached data serialized given the new structure --- zork++/benches/benchmarks.rs | 8 ++++---- zork++/src/lib/cache/mod.rs | 25 +++++++++++++++---------- zork++/src/lib/lib.rs | 10 +++++----- zork++/src/lib/project_model/mod.rs | 19 ++++++++++--------- zork++/test/test.rs | 4 ++-- 5 files changed, 36 insertions(+), 30 deletions(-) diff --git a/zork++/benches/benchmarks.rs b/zork++/benches/benchmarks.rs index aa8afa61..6b8fdce1 100644 --- a/zork++/benches/benchmarks.rs +++ b/zork++/benches/benchmarks.rs @@ -6,7 +6,7 @@ use clap::Parser; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use zork::compiler::generate_commands; use zork::{ - cache::{self, ZorkCache}, + cache::ZorkCache, cli::input::CliArgs, config_file::{self, ZorkConfigFile}, utils::{self, reader::build_model}, @@ -23,9 +23,9 @@ pub fn build_project_benchmark(c: &mut Criterion) { b.iter(|| generate_commands(black_box(&program_data), black_box(&mut cache), &cli_args)) }); - c.bench_function("Cache loading time", |b| { - b.iter(|| cache::load(black_box(&program_data), &CliArgs::default())) - }); + /* c.bench_function("Cache loading time", |b| { + b.iter(|| cache::load(black_box(&config), &CliArgs::default(), &Path::new("."))) + }); */ } criterion_group!(benches, build_project_benchmark); diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index c7ebe9c6..487d39bc 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -32,17 +32,22 @@ use crate::project_model::compiler::StdLibMode; /// Standalone utility for load from the file system the Zork++ cache file /// for the target [`CppCompiler`] -pub fn load<'a>(config: &ZorkConfigFile<'a>, cli_args: &CliArgs) -> Result> { +pub fn load<'a>( + config: &ZorkConfigFile<'a>, + cli_args: &CliArgs, + project_root: &Path, +) -> Result> { let compiler: CppCompiler = config.compiler.cpp_compiler.into(); - let cache_path = Path::new( - &config - .build - .as_ref() - .and_then(|build_attr| build_attr.output_dir) - .unwrap_or("out"), - ) - .join("zork") - .join("cache"); + let cache_path = Path::new(project_root) + .join( + config + .build + .as_ref() + .and_then(|build_attr| build_attr.output_dir) + .unwrap_or("out"), + ) + .join("zork") + .join("cache"); let cache_file_path = cache_path .join(compiler.as_ref()) diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index a59201f4..af4d05c9 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -80,8 +80,8 @@ pub mod worker { let config = config_file::zork_cfg_from_file(raw_file.as_str()) .with_context(|| error_messages::PARSE_CFG_FILE)?; - create_output_directory(&config)?; // TODO: avoid this call without check if exists - let cache = cache::load(&config, cli_args)?; + create_output_directory(&config, &abs_project_root)?; // TODO: avoid this call without check if exists + let cache = cache::load(&config, cli_args, &abs_project_root)?; // TODO: Big one, need to call cache.load_tasks or whatever, or metadata won't be // loaded @@ -155,7 +155,7 @@ pub mod worker { /// - a /cache folder, where lives the metadata cached by Zork++ /// in order to track different aspects of the program (last time /// modified files, last process build time...) - fn create_output_directory(config: &ZorkConfigFile) -> Result<()> { + fn create_output_directory(config: &ZorkConfigFile, project_root: &Path) -> Result<()> { let compiler: CppCompiler = config.compiler.cpp_compiler.into(); let compiler_name = compiler.as_ref(); let binding = config @@ -163,7 +163,7 @@ pub mod worker { .as_ref() .and_then(|build_attr| build_attr.output_dir) .unwrap_or("out"); - let out_dir = Path::new(&binding); + let out_dir = Path::new(project_root).join(binding); // Recursively create the directories below and all of its parent components if they are missing let modules_path = out_dir.join(compiler_name).join(dir_names::MODULES); @@ -228,7 +228,7 @@ pub mod worker { let modules_path = compiler_folder_dir.join("modules"); // This should create and out/ directory at the root of the tmp path - super::create_output_directory(&zcf)?; + super::create_output_directory(&zcf, temp_path)?; assert!(out_dir.exists()); diff --git a/zork++/src/lib/project_model/mod.rs b/zork++/src/lib/project_model/mod.rs index 5dca2dde..ad067913 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -49,15 +49,16 @@ pub fn load<'a>( absolute_project_root: &Path, ) -> Result> { let compiler: CppCompiler = config.compiler.cpp_compiler.into(); - let cache_path = Path::new( - &config - .build - .as_ref() - .and_then(|build_attr| build_attr.output_dir) - .unwrap_or("out"), - ) - .join("zork") - .join("cache"); + let cache_path = Path::new(absolute_project_root) + .join( + config + .build + .as_ref() + .and_then(|build_attr| build_attr.output_dir) + .unwrap_or("out"), + ) + .join("zork") + .join("cache"); let cached_project_model_path = cache_path .join(format!("{}_pm", compiler.as_ref())) diff --git a/zork++/test/test.rs b/zork++/test/test.rs index ab36b4a0..871fd5b0 100644 --- a/zork++/test/test.rs +++ b/zork++/test/test.rs @@ -35,8 +35,8 @@ fn test_clang_full_process() -> Result<()> { "-vv", "--root", &project_root, - /* "--driver-path", - "clang++-16", // Local cfg issues */ + "--driver-path", + "clang++-16", // Local cfg issues "run", ])); assert!(process_result.is_ok(), "{}", process_result.unwrap_err()); From 52cc0f46098e2a2c8bb8efa0745431af7ca56861 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 14 Jul 2024 19:54:20 +0200 Subject: [PATCH 38/73] fix: Only loading the cache metadata on the clean runs --- zork++/src/lib/cache/mod.rs | 66 +++++++++++++++++-------------- zork++/src/lib/lib.rs | 4 ++ zork++/src/lib/utils/constants.rs | 2 + 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 487d39bc..850b5fff 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -38,16 +38,14 @@ pub fn load<'a>( project_root: &Path, ) -> Result> { let compiler: CppCompiler = config.compiler.cpp_compiler.into(); - let cache_path = Path::new(project_root) - .join( - config - .build - .as_ref() - .and_then(|build_attr| build_attr.output_dir) - .unwrap_or("out"), - ) - .join("zork") - .join("cache"); + let output_dir = Path::new(project_root).join( + config + .build + .as_ref() + .and_then(|build_attr| build_attr.output_dir) + .unwrap_or("out"), + ); + let cache_path = &output_dir.join("zork").join("cache"); let cache_file_path = cache_path .join(compiler.as_ref()) @@ -58,14 +56,14 @@ pub fn load<'a>( // Or just ...//_.json let cache = if !cache_file_path.exists() { File::create(&cache_file_path).with_context(|| "Error creating the cache file")?; - helpers::initialize_default_cache(cache_file_path)? + helpers::initialize_default_cache(cache_file_path, compiler, &output_dir)? } else if cache_path.exists() && cli_args.clear_cache { - fs::remove_dir_all(&cache_path).with_context(|| "Error cleaning the Zork++ cache")?; + fs::remove_dir_all(cache_path).with_context(|| "Error cleaning the Zork++ cache")?; fs::create_dir(cache_path) .with_context(|| "Error creating the cache subdirectory for {compiler}")?; File::create(&cache_file_path) .with_context(|| "Error creating the cache file after cleaning the cache")?; - helpers::initialize_default_cache(cache_file_path)? + helpers::initialize_default_cache(cache_file_path, compiler, &output_dir)? } else { log::trace!( "Loading Zork++ cache file for {compiler} at: {:?}", @@ -184,10 +182,9 @@ impl<'a> ZorkCache<'a> { } /// The tasks associated with the cache after load it from the file system - pub fn run_tasks(&mut self, program_data: &'a ZorkModel<'_>) -> Result<()> { - let compiler = program_data.compiler.cpp_compiler; - if cfg!(target_os = "windows") && compiler == CppCompiler::MSVC { - msvc::load_metadata(self, program_data)? + pub fn run_tasks(&mut self, compiler: CppCompiler, output_dir: &Path) -> Result<()> { + if cfg!(target_os = "windows") && compiler.eq(&CppCompiler::MSVC) { + msvc::load_metadata(self, compiler, output_dir)? } Ok(()) @@ -287,18 +284,18 @@ pub struct GccMetadata { /// Helper procedures to process cache data for Microsoft's MSVC mod msvc { use crate::cache::ZorkCache; + use crate::project_model::compiler::CppCompiler; use crate::project_model::sourceset::SourceFile; - use crate::project_model::ZorkModel; use crate::utils; - use crate::utils::constants; use crate::utils::constants::error_messages; + use crate::utils::constants::{self, dir_names}; use color_eyre::eyre::{eyre, Context, ContextCompat, OptionExt}; use regex::Regex; use std::borrow::Cow; use std::collections::HashMap; use std::path::Path; - /// If Windows is the current OS, and the compiler is MSVC, then we will try + /// If *Windows* is the current OS, and the compiler is *MSVC*, then we will try /// to locate the path of the `vcvars64.bat` script that will set a set of environmental /// variables that are required to work effortlessly with the Microsoft's compiler. /// @@ -307,13 +304,12 @@ mod msvc { /// run this process once per new cache created (cache action 1) pub(crate) fn load_metadata( cache: &mut ZorkCache, - program_data: &ZorkModel<'_>, + compiler: CppCompiler, + output_dir: &Path, ) -> color_eyre::Result<()> { let msvc = &mut cache.compilers_metadata.msvc; if msvc.dev_commands_prompt.is_none() { - let compiler = program_data.compiler.cpp_compiler; - msvc.dev_commands_prompt = utils::fs::find_file( Path::new(constants::MSVC_REGULAR_BASE_PATH), constants::MS_ENV_VARS_BAT, @@ -342,7 +338,7 @@ mod msvc { .get("VCToolsInstallDir") .with_context(|| error_messages::msvc::MISSING_VCTOOLS_DIR)?; - let vs_stdlib_path = Path::new(vctools_dir).join("modules"); + let vs_stdlib_path = Path::new(vctools_dir).join(dir_names::MODULES); if !vs_stdlib_path.exists() { return Err(eyre!(error_messages::msvc::STDLIB_MODULES_NOT_FOUND)); } @@ -357,10 +353,10 @@ mod msvc { file_stem: Cow::Borrowed("std.compat"), extension: compiler.default_module_extension(), }; - let modular_stdlib_byproducts_path = Path::new(&program_data.build.output_dir) + let modular_stdlib_byproducts_path = Path::new(output_dir) .join(compiler.as_ref()) - .join("modules") - .join("std") // folder + .join(dir_names::MODULES) + .join(dir_names::STD) // folder .join("std"); // filename // Saving the paths to the precompiled bmi and obj files of the MSVC std implementation @@ -404,11 +400,17 @@ mod msvc { } mod helpers { + use self::utils::constants::error_messages; + use super::*; use std::path::PathBuf; - pub(crate) fn initialize_default_cache<'a>(cache_file_path: PathBuf) -> Result> { - let default_initialized = ZorkCache { + pub(crate) fn initialize_default_cache<'a>( + cache_file_path: PathBuf, + compiler: CppCompiler, + output_dir: &Path, + ) -> Result> { + let mut default_initialized = ZorkCache { metadata: CacheMetadata { cache_file_path: cache_file_path.clone(), ..Default::default() @@ -417,7 +419,11 @@ mod helpers { }; utils::fs::serialize_object_to_file(&cache_file_path, &default_initialized) - .with_context(move || "Error saving data to the Zork++ cache")?; + .with_context(|| error_messages::FAILURE_SAVING_CACHE)?; + + default_initialized + .run_tasks(compiler, output_dir) + .with_context(|| error_messages::FAILURE_LOADING_INITIAL_CACHE_DATA)?; Ok(default_initialized) } diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index af4d05c9..775bf32d 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -85,7 +85,11 @@ pub mod worker { // TODO: Big one, need to call cache.load_tasks or whatever, or metadata won't be // loaded + // TODO: also, we're missing the last modification check, to now if we must force to + // remap due to changes on the related cfg let program_data = project_model::load(config, cli_args, &abs_project_root)?; + // TODO: maybe couple them a little bit, and return a (cache, program_data) structured + // binding? do_main_work_based_on_cli_input(cli_args, &program_data, cache).with_context(|| { format!( diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index 169212c2..1bdfc3e7 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -20,6 +20,8 @@ pub mod error_messages { pub const FAILURE_GENERATING_COMMANDS: &str = "Failed to generated the commands for the project"; pub const FAILED_BUILD_FOR_CFG_FILE: &str = "Failed to build the project for the config file"; + pub const FAILURE_LOADING_INITIAL_CACHE_DATA: &str = "Failed to load the cache initial data"; + pub const FAILURE_SAVING_CACHE: &str = "Error saving data to the Zork++ cache"; pub const GENERAL_ARGS_NOT_FOUND: &str = "Something went wrong loading the general arguments"; pub const PROJECT_MODEL_MAPPING: &str = "Error building the project model"; pub const PROJECT_MODEL_SAVE: &str = "Error caching and saving to the fs the project model"; From 2cbb6d66a258b4d902120c559755bcf915dd66e9 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 14 Jul 2024 21:04:55 +0200 Subject: [PATCH 39/73] chore: small opts and docs --- zork++/src/lib/cache/mod.rs | 32 ++++++++++------- zork++/src/lib/cli/output/commands.rs | 35 ++----------------- zork++/src/lib/compiler/data_factory.rs | 22 +++++------- zork++/src/lib/compiler/mod.rs | 11 ++---- zork++/src/lib/lib.rs | 34 +++++++++++------- zork++/src/lib/project_model/mod.rs | 14 +++++--- zork++/src/lib/utils/constants.rs | 18 ++++++++++ zork++/src/lib/utils/reader.rs | 5 ++- zork++/src/lib/utils/template/mod.rs | 46 ------------------------- 9 files changed, 84 insertions(+), 133 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 850b5fff..18a75968 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -141,7 +141,7 @@ impl<'a> ZorkCache<'a> { /// Gets the target [`SystemModule`] generated [`SourceCommandLine`] from the cache /// - /// TODO: Since we don't implement the lookup of the directory of the installed system headers, + /// NOTE: While we don't implement the lookup of the directory of the installed system headers, /// we are using some tricks to matching the generated command, but is not robust fn get_system_module_cmd>( &mut self, @@ -209,7 +209,7 @@ impl<'a> ZorkCache<'a> { } } - // TODO: read_only_iterator (better name) and docs pls + /// Returns a view of borrowed data over all the generated commands for a target pub fn get_all_commands_iter(&self) -> impl Iterator + Debug + '_ { let generated_commands = &self.generated_commands; @@ -223,18 +223,22 @@ impl<'a> ZorkCache<'a> { .chain(generated_commands.sources.iter()) } + /// The current integer value that is the total of commands generated for all the + /// [`TranslationUnit`] declared in the user's configuration file, without counting the linker + /// one for the current target pub fn count_total_generated_commands(&self) -> usize { let latest_commands = &self.generated_commands; latest_commands.interfaces.len() + latest_commands.implementations.len() + latest_commands.sources.len() - // + latest_commands.pre_tasks.len() + + latest_commands.system_modules.len() + 2 // the cpp_stdlib and the c_compat_stdlib - // + 1 // TODO: the linker one? Does it supports it clangd? } } +/// A struct for holding Zork++ internal details about its configuration, procedures or runtime +/// statuses #[derive(Deserialize, Serialize, Debug, Default, Clone)] pub struct CacheMetadata { pub process_no: i32, @@ -249,7 +253,6 @@ pub type EnvVars = HashMap; #[derive(Deserialize, Serialize, Debug, Default, Clone)] pub struct CompilersMetadata<'a> { - // TODO: apply the same solution: have a fat pointer or better convert them into a Union/enum? pub msvc: MsvcMetadata<'a>, pub clang: ClangMetadata, pub gcc: GccMetadata, @@ -287,8 +290,8 @@ mod msvc { use crate::project_model::compiler::CppCompiler; use crate::project_model::sourceset::SourceFile; use crate::utils; - use crate::utils::constants::error_messages; use crate::utils::constants::{self, dir_names}; + use crate::utils::constants::{env_vars, error_messages}; use color_eyre::eyre::{eyre, Context, ContextCompat, OptionExt}; use regex::Regex; use std::borrow::Cow; @@ -322,20 +325,22 @@ mod msvc { }); let output = std::process::Command::new(constants::WIN_CMD) .arg("/c") - .arg(msvc.dev_commands_prompt.as_ref().ok_or_eyre("Zork++ wasn't unable to find the VS env vars")?) + .arg(msvc.dev_commands_prompt.as_ref().ok_or_eyre( + error_messages::msvc::MISSING_OR_CORRUPTED_MSVC_DEV_COMMAND_PROMPT, + )?) .arg("&&") .arg("set") .output() - .with_context(|| "Unable to load MSVC pre-requisites. Please, open an issue with the details on upstream")?; + .with_context(|| error_messages::msvc::FAILURE_LOADING_VS_ENV_VARS)?; msvc.env_vars = load_env_vars_from_cmd_output(&output.stdout)?; // Cloning the useful ones for quick access at call site - msvc.compiler_version = msvc.env_vars.get("VisualStudioVersion").cloned(); + msvc.compiler_version = msvc.env_vars.get(env_vars::VS_VERSION).cloned(); // Check the existence of the VCtools let vctools_dir = msvc .env_vars - .get("VCToolsInstallDir") + .get(env_vars::VC_TOOLS_INSTALL_DIR) .with_context(|| error_messages::msvc::MISSING_VCTOOLS_DIR)?; let vs_stdlib_path = Path::new(vctools_dir).join(dir_names::MODULES); @@ -367,7 +372,7 @@ mod msvc { modular_stdlib_byproducts_path.with_extension(compiler.get_obj_file_extension()); let c_modular_stdlib_byproducts_path = modular_stdlib_byproducts_path; - let compat = String::from("compat."); // TODO: find a better way + let compat = String::from("compat."); msvc.ccompat_stdlib_bmi_path = c_modular_stdlib_byproducts_path .with_extension(compat.clone() + compiler.get_typical_bmi_extension()); msvc.ccompat_stdlib_obj_path = c_modular_stdlib_byproducts_path @@ -387,7 +392,10 @@ mod msvc { for line in env_vars_str.lines() { // Parse the key-value pair from each line let mut parts = line.splitn(2, '='); - let key = parts.next().expect("Failed to get key").trim(); + let key = parts + .next() + .expect(error_messages::msvc::ILL_FORMED_KEY_ON_ENV_VARS_PARSING) + .trim(); if filter.is_match(key) { let value = parts.next().unwrap_or_default().trim().to_string(); diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index fbd9f2fd..111bbe45 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -3,7 +3,6 @@ use std::ffi::OsStr; use std::fmt::Debug; -use std::slice::Iter; use std::{ path::{Path, PathBuf}, process::ExitStatus, @@ -251,14 +250,11 @@ impl<'a> LinkerCommandLine<'a> { /// Holds the generated command line arguments for a concrete compiler #[derive(Serialize, Deserialize, Default, Debug)] pub struct Commands<'a> { - pub cpp_stdlib: Option>, - pub c_compat_stdlib: Option>, - // pub system_modules: HashMap, - pub system_modules: Vec>, // TODO: SourceCommandLine while we found a better approach - // or while we don't implement the parser that gets the path to the compilers std library headers pub general_args: Option>, pub compiler_common_args: Option>, - + pub cpp_stdlib: Option>, + pub c_compat_stdlib: Option>, + pub system_modules: Vec>, pub interfaces: Vec>, pub implementations: Vec>, pub sources: Vec>, @@ -289,31 +285,6 @@ impl<'a> Commands<'a> { } } -impl<'a> core::fmt::Display for Commands<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Commands:\n- Interfaces: {:?},\n- Implementations: {:?},\n- Sources: {:?}", - collect_source_command_line(self.interfaces.iter()), - collect_source_command_line(self.implementations.iter()), - collect_source_command_line(self.sources.iter()) - ) - } -} - -/// Convenient function to avoid code replication -fn collect_source_command_line<'a>( - iter: Iter<'a, SourceCommandLine>, // TODO: review this, for see if it's possible to consume the value and not cloning it -) -> impl Iterator + Debug + 'a { - iter.map(|vec| { - vec.args - .iter() - .map(|arg| arg.value().clone()) - .collect::>() - .join(" "); - }) -} - /// The different states of a translation unit in the whole lifecycle of /// the build process and across different iterations of the same #[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq)] diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index 9102a227..6961fa46 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -2,17 +2,18 @@ //! translation unit, having shared data without replicating it until the final command line must //! be generated in order to be stored (in cache) and executed (in the underlying shell) -use std::{borrow::Cow, path::Path}; - -use serde::{Deserialize, Serialize}; - use crate::{ cache::ZorkCache, cli::output::arguments::{clang_args, Argument, Arguments}, domain::target::ExtraArgs, - project_model::compiler::{CppCompiler, StdLib}, - project_model::ZorkModel, + project_model::{ + compiler::{CppCompiler, StdLib}, + ZorkModel, + }, + utils::constants::error_messages, }; +use serde::{Deserialize, Serialize}; +use std::{borrow::Cow, path::Path}; /// Holds the common arguments across all the different command lines regarding the target compiler /// @@ -56,8 +57,6 @@ pub fn compiler_common_arguments_factory( model: &ZorkModel<'_>, cache: &ZorkCache<'_>, ) -> Box { - // TODO: consider having a union (enum) instead of a fat ptr, so we can serialize the data - // and introduce a lifetime on the Argument type to use Cow instead of String match model.compiler.cpp_compiler { CppCompiler::CLANG => Box::new(ClangCommonArgs::new(model)), CppCompiler::MSVC => Box::new(MsvcCommonArgs::new(model, cache)), @@ -73,14 +72,11 @@ pub trait CompilerCommonArguments: std::fmt::Debug { } impl Default for Box { fn default() -> Self { - Box::::default() // TODO: isn't this a code smell? - // TODO: should we just panic? Or maybe fix the default? Or maybe have an associated - // and pass the compiler to the trait fn? So we can ensure that the default has sense? - // TODO: we can just fix as well the serialization function, removing the default + panic!("{}", error_messages::DEFAULT_OF_COMPILER_COMMON_ARGUMENTS) } } -/// TODO: the typetag library doesn't support yet the deserialization of generic impls, only +/// NOTE: the typetag library doesn't support yet the deserialization of generic impls, only /// serialization, so there's no point on having any primites #[typetag::serde] impl CompilerCommonArguments for ClangCommonArgs { diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index f2de2225..ba6b649b 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -79,7 +79,6 @@ fn load_flyweights_for_general_shared_data<'a>(model: &'a ZorkModel, cache: &mut /// Generates the cmds for build the C++ standard libraries (std and std.compat) according to the specification /// of each compiler vendor fn generate_modular_stdlibs_cmds<'a>(model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>) { - // TODO: remaining ones: Clang, GCC. // NOTE: Provisionally 'If' guarded because only MSVC is supported now to build the // C++ standard library implementations if model.compiler.cpp_compiler.eq(&CppCompiler::MSVC) { @@ -149,9 +148,9 @@ fn generate_sources_cmds_args<'a>( cli_args: &'a CliArgs, ) -> Result<()> { log::info!("Generating the commands for the source files..."); - // TODO: tests manual run must be start to be deprecated in favour of the future - // named targets, so we won't mess now with them + let is_tests_run = cli_args.command.eq(&Command::Test); + let srcs = if is_tests_run { &model.tests.sourceset.sources } else { @@ -444,7 +443,6 @@ mod modules { /// System headers can be imported as modules, but they must be built before being imported. /// /// This feature is supported by `GCC` and `Clang` - /// NOTE: With the inclusion of std named modules, want we to support this anymore? pub(crate) fn generate_sys_module_cmd<'a>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, @@ -507,8 +505,6 @@ mod modules { let scl = msvc_args::generate_std_cmd(cache, stdlib_mode); cache.set_cpp_stdlib_cmd_by_kind(stdlib_mode, Some(scl)); - // TODO: see the Some(scl) above? well, implement the generators for the other compilers - // and just return optional none?, so we can get rid out of all the todo } } } @@ -679,9 +675,6 @@ mod helpers { cached_source_cmd: &SourceCommandLine, ) -> bool { if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { - // TODO: check on a Linux distro - // that our cache doesn't collide with the clang modules cache, or just skip clang's cache - // with a cmd arg if possible log::trace!("Module unit {:?} will be rebuilt since we've detected that you are using Clang in Windows", cached_source_cmd.path()); return true; } diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 775bf32d..96027fe8 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -65,8 +65,7 @@ pub mod worker { ); }; - let config_files: Vec = find_config_files(project_root, &cli_args.match_files) - .with_context(|| "We didn't found a valid Zork++ configuration file")?; + let config_files: Vec = find_config_files(project_root, &cli_args.match_files)?; for config_file in config_files { let cfg_path = &config_file.path; @@ -77,19 +76,19 @@ pub mod worker { let raw_file = fs::read_to_string(cfg_path) .with_context(|| format!("{}: {:?}", error_messages::READ_CFG_FILE, cfg_path))?; - let config = config_file::zork_cfg_from_file(raw_file.as_str()) + let config: ZorkConfigFile<'_> = config_file::zork_cfg_from_file(raw_file.as_str()) .with_context(|| error_messages::PARSE_CFG_FILE)?; - create_output_directory(&config, &abs_project_root)?; // TODO: avoid this call without check if exists - let cache = cache::load(&config, cli_args, &abs_project_root)?; - // TODO: Big one, need to call cache.load_tasks or whatever, or metadata won't be - // loaded + create_output_directory(&config, &abs_project_root)?; - // TODO: also, we're missing the last modification check, to now if we must force to - // remap due to changes on the related cfg - let program_data = project_model::load(config, cli_args, &abs_project_root)?; - // TODO: maybe couple them a little bit, and return a (cache, program_data) structured - // binding? + let cache: ZorkCache<'_> = cache::load(&config, cli_args, &abs_project_root)?; + + let program_data: ZorkModel<'_> = + if config_file.last_time_modified > cache.metadata.last_program_execution { + utils::reader::build_model(config, cli_args, &abs_project_root)? + } else { + project_model::load(config, cli_args, &abs_project_root)? + }; do_main_work_based_on_cli_input(cli_args, &program_data, cache).with_context(|| { format!( @@ -120,6 +119,8 @@ pub mod worker { // // other is to have just a separate function that only passes the required data // like cache to be modified and the new ones + // + // TODO: introduce debug times for the main processes generate_commands(program_data, &mut cache, cli_args) .with_context(|| error_messages::FAILURE_GENERATING_COMMANDS)?; @@ -169,6 +170,13 @@ pub mod worker { .unwrap_or("out"); let out_dir = Path::new(project_root).join(binding); + if out_dir.exists() { + return Ok(()); + } // early guard. If the out_dir already exists, all + // the sub-structure must exists and be correct. + // Otherwise, a full out dir wipe will be preferable + // that checking if they all exists on every run + // Recursively create the directories below and all of its parent components if they are missing let modules_path = out_dir.join(compiler_name).join(dir_names::MODULES); @@ -185,7 +193,7 @@ pub mod worker { utils::fs::create_directory(&zork_cache_path)?; utils::fs::create_directory(&zork_intrinsics_path)?; - // TODO: This possibly gonna be temporary + // Pre Clang-18 way if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { utils::fs::create_file( &zork_intrinsics_path, diff --git a/zork++/src/lib/project_model/mod.rs b/zork++/src/lib/project_model/mod.rs index ad067913..0dd25974 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -17,7 +17,7 @@ use crate::{ config_file::ZorkConfigFile, utils::{ self, - constants::{error_messages, CACHE_FILE_EXT}, + constants::{debug_messages, error_messages, CACHE_FILE_EXT}, reader, }, }; @@ -66,10 +66,14 @@ pub fn load<'a>( utils::fs::load_and_deserialize::(&cached_project_model_path) .or_else(|_| { - log::debug!("Proceding to map the configuration file to the ZorkModel entity, since no cached project model was found"); - let program_data: ZorkModel = reader::build_model(config, cli_args, absolute_project_root)?; - utils::fs::serialize_object_to_file::(&cached_project_model_path, &program_data) - .with_context(|| error_messages::PROJECT_MODEL_SAVE)?; + log::debug!("{}", debug_messages::MAPPING_CFG_TO_MODEL); + let program_data: ZorkModel = + reader::build_model(config, cli_args, absolute_project_root)?; + utils::fs::serialize_object_to_file::( + &cached_project_model_path, + &program_data, + ) + .with_context(|| error_messages::PROJECT_MODEL_SAVE)?; Ok::, color_eyre::eyre::Error>(program_data) }) diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index 1bdfc3e7..2affeb54 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -14,6 +14,15 @@ pub mod dir_names { pub const OBJECT_FILES: &str = "obj_files"; } +pub mod env_vars { + pub const VS_VERSION: &str = "VisualStudioVersion"; + pub const VC_TOOLS_INSTALL_DIR: &str = "VCToolsInstallDir"; +} + +pub mod debug_messages { + pub const MAPPING_CFG_TO_MODEL: &str = "Proceding to map the configuration file to the ZorkModel entity, since no cached project model was found"; +} + pub mod error_messages { pub const READ_CFG_FILE: &str = "Could not read the configuration file"; pub const PARSE_CFG_FILE: &str = "Could not parse the configuration file"; @@ -27,6 +36,9 @@ pub mod error_messages { pub const PROJECT_MODEL_SAVE: &str = "Error caching and saving to the fs the project model"; pub const COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND: &str = "Something went wrong loading the general arguments"; + pub const DEFAULT_OF_COMPILER_COMMON_ARGUMENTS: &str = + "Reached the default implementation of the CompilerCommonArgument data structure.\ + This is a bug, so please, report it by opening an issue on https://github.com/zerodaycode/Zork/issues"; pub const CLI_ARGS_CMD_NEW_BRANCH: &str = "This branch should never be reached for now, as do not exists commands that may\ trigger them. The unique remaining, is ::New, that is already processed\ @@ -37,6 +49,12 @@ pub mod error_messages { "Can't find the MSVC standard library modules. Did you installed them?"; pub const MISSING_VCTOOLS_DIR: &str = "Unable to find MSVC VCToolsInstallDir. Did you installed the required C++ tools for the compiler?"; + pub const FAILURE_LOADING_VS_ENV_VARS: &str = + "Zork++ wasn't unable to find the VS env vars"; + pub const ILL_FORMED_KEY_ON_ENV_VARS_PARSING: &str = + "Ill-formed key while parsing MSVC env vars"; + pub const MISSING_OR_CORRUPTED_MSVC_DEV_COMMAND_PROMPT: &str = + "Missing or corrupted path for the MSVC developers command prompt"; } } diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 367fc268..c7e3ced9 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -59,7 +59,7 @@ pub fn find_config_files( base_path: &Path, // TODO: create the cfg arg to specifically receive where's located the // user's Zork config files if they're not in the root of the project // nor matches the tree structure from user's root cmd arg value - filename_match: &Option, // TODO: this shoudn't be necessary + filename_match: &Option, ) -> Result> { log::debug!("Searching for Zork++ configuration files..."); let mut files = vec![]; @@ -156,7 +156,7 @@ fn assemble_compiler_model<'a>( driver_path: if let Some(driver_path) = cli_args.driver_path.as_ref() { Cow::Borrowed(driver_path) } else { - Cow::Owned(cli_args.driver_path.clone().unwrap_or_default()) // TODO: review this + Cow::Owned(cli_args.driver_path.clone().unwrap_or_default()) }, cpp_standard: config.cpp_standard.clone().into(), std_lib: config.std_lib.clone().map(|lib| lib.into()), @@ -325,7 +325,6 @@ fn assemble_module_implementation_model<'a>( let file_path = Path::new(project_root).join(base_path).join(config.file); if dependencies.is_empty() { - // TODO: can't recall what's this, so please, debug it and document it let last_dot_index = config.file.rfind('.'); if let Some(idx) = last_dot_index { let implicit_dependency = config.file.split_at(idx); diff --git a/zork++/src/lib/utils/template/mod.rs b/zork++/src/lib/utils/template/mod.rs index 80bf4c1b..99828b2b 100644 --- a/zork++/src/lib/utils/template/mod.rs +++ b/zork++/src/lib/utils/template/mod.rs @@ -123,52 +123,6 @@ pub fn create_templated_project( Ok(()) } -/* -* TODO: pending to be implemented when we decide if it's worth to update to the newest trash -* versions of the crate `toml`, and procedurally generate the config files (w/o templates) -* - let mut config: ZorkConfigFile<'_> = config_file::zork_cfg_from_file(template) - .with_context(|| "Could not parse the template configuration file")?; - println!("Zork config loaded: {:?}", &config); - - config.project.name = project_name; - match compiler { - CppCompiler::CLANG => { - config.compiler.cpp_compiler = CfgCppCompiler::CLANG; - config.compiler.cpp_standard = LanguageLevel::CPP2B; - config.compiler.std_lib = Some(StdLib::LIBCPP); - } - CppCompiler::MSVC => { - config.compiler.cpp_compiler = CfgCppCompiler::MSVC; - config.compiler.cpp_standard = LanguageLevel::LATEST; - config.compiler.std_lib = None; - } - CppCompiler::GCC => { - config.compiler.cpp_compiler = CfgCppCompiler::GCC; - config.compiler.cpp_standard = LanguageLevel::CPP20; - config.compiler.std_lib = Some(StdLib::STDLIBCPP); - } - } - - let exec = config.executable.as_mut().unwrap(); - exec.executable_name = Some(project_name); - exec.sources = vec!["*.cpp"].into(); // TDOO aggg sove this - - let tests = config.tests.as_mut().unwrap(); - tests.sources = vec!["*.cpp"].into(); // TDOO aggg sove this - - let modules = config.modules.as_mut().unwrap(); - modules.base_ifcs_dir = Some("ifc"); - modules.base_impls_dir = Some("src"); - - println!("Zork config after: {:?}", &config); - let conf_as_str = toml::to_string(config.borrow()) - .with_context(|| "Failed to serialize the `ZorkConfigFile` of the template")? - .replace("cppm", compiler.get_default_module_extension()); // TODO: yet this legacy - // replace... - -*/ - fn check_project_root_available(project_root: &Path) -> Result<()> { if !project_root.exists() { // if it doesn't exist, there is nothing that would be overwritten From aa7e43e50d8eea8e9614aa29d3d50ab579624285 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 15 Jul 2024 09:08:40 +0200 Subject: [PATCH 40/73] feat: upgrading the .unwrap() on the downcast of the translation units to handle results --- zork++/src/lib/compiler/mod.rs | 82 ++++++++++++++++++------------- zork++/src/lib/utils/constants.rs | 10 ++++ 2 files changed, 57 insertions(+), 35 deletions(-) diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index ba6b649b..474d2fa3 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -3,12 +3,14 @@ //! operating system against the designed compilers in the configuration //! file. +use color_eyre::eyre::{Context, ContextCompat}; use std::path::Path; use color_eyre::Result; use crate::cli::output::commands::TranslationUnitStatus; use crate::project_model::modules::SystemModule; +use crate::utils::constants::error_messages; use crate::{ cache::ZorkCache, cli::{ @@ -47,7 +49,7 @@ pub fn generate_commands<'a>( // Pre-tasks if model.compiler.cpp_compiler != CppCompiler::MSVC && !model.modules.sys_modules.is_empty() { - generate_sys_modules_commands(model, cache, cli_args); + generate_sys_modules_commands(model, cache, cli_args)?; } // Translation units and linker @@ -95,14 +97,15 @@ fn generate_sys_modules_commands<'a>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, cli_args: &'a CliArgs, -) { +) -> Result<()> { process_kind_translation_units( model, cache, cli_args, &model.modules.sys_modules, TranslationUnitKind::SystemHeader, - ); + ) + .with_context(|| error_messages::FAILURE_SYSTEM_MODULES) } /// The procedure that takes care of generating the [`SourceCommandLine`] to build the user's declared @@ -121,7 +124,8 @@ fn process_modules<'a>( cli_args, &modules.interfaces, TranslationUnitKind::ModuleInterface, - ); + ) + .with_context(|| error_messages::FAILURE_MODULE_INTERFACES)?; log::info!("Generating the commands for the module implementations and partitions..."); process_kind_translation_units( @@ -130,7 +134,8 @@ fn process_modules<'a>( cli_args, &modules.implementations, TranslationUnitKind::ModuleImplementation, - ); + ) + .with_context(|| error_messages::FAILURE_MODULE_IMPLEMENTATIONS)?; Ok(()) } @@ -163,9 +168,8 @@ fn generate_sources_cmds_args<'a>( cli_args, srcs, TranslationUnitKind::SourceFile, - ); - - Ok(()) + ) + .with_context(|| error_messages::FAILURE_TARGET_SOURCES) } /// Generates the command line that will be passed to the linker to generate an [`ExecutableTarget`] @@ -205,12 +209,11 @@ pub fn generate_linker_general_command_line_args<'a>( let compiler = &model.compiler.cpp_compiler; let out_dir: &Path = model.build.output_dir.as_ref(); - let executable_name = target.name(); let target_output = Argument::from( out_dir .join(compiler.as_ref()) - .join(executable_name) + .join(target.name()) .with_extension(constants::BINARY_EXTENSION), ); @@ -240,10 +243,12 @@ fn process_kind_translation_units<'a, T: TranslationUnit<'a>>( cli_args: &'a CliArgs, translation_units: &'a [T], for_kind: TranslationUnitKind, -) { +) -> Result<()> { for translation_unit in translation_units.iter() { - process_kind_translation_unit(model, cache, cli_args, translation_unit, &for_kind) + process_kind_translation_unit(model, cache, cli_args, translation_unit, &for_kind)? } + + Ok(()) } fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( @@ -252,7 +257,7 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( cli_args: &'a CliArgs, translation_unit: &'a T, for_kind: &TranslationUnitKind, -) { +) -> Result<()> { let compiler = model.compiler.cpp_compiler; let lpe = cache.metadata.last_program_execution; @@ -268,22 +273,20 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( generated_cmd.status = build_translation_unit; } else { let tu_with_erased_type = translation_unit.as_any(); - // TODO: remove the .unwrap() (s) below for some other robust solution + match &for_kind { TranslationUnitKind::ModuleInterface => { let resolved_tu = - transient::Downcast::downcast_ref::(tu_with_erased_type); - modules::generate_module_interface_cmd(model, cache, resolved_tu.unwrap()); + transient::Downcast::downcast_ref::(tu_with_erased_type) + .with_context(|| helpers::wrong_downcast_msg(translation_unit))?; + modules::generate_module_interface_cmd(model, cache, resolved_tu); } TranslationUnitKind::ModuleImplementation => { - modules::generate_module_implementation_cmd( - model, - cache, - transient::Downcast::downcast_ref::( - tu_with_erased_type, - ) - .unwrap(), + let resolved_tu = transient::Downcast::downcast_ref::( + tu_with_erased_type, ) + .with_context(|| helpers::wrong_downcast_msg(translation_unit))?; + modules::generate_module_implementation_cmd(model, cache, resolved_tu) } TranslationUnitKind::SourceFile => { let target = if cli_args.command.eq(&Command::Test) { @@ -291,21 +294,22 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( } else { &model.executable as &dyn ExecutableTarget }; - sources::generate_sources_arguments( - model, - cache, - transient::Downcast::downcast_ref::(tu_with_erased_type).unwrap(), - target, - ) + let resolved_tu = + transient::Downcast::downcast_ref::(tu_with_erased_type) + .with_context(|| helpers::wrong_downcast_msg(translation_unit))?; + sources::generate_sources_arguments(model, cache, resolved_tu, target) } - TranslationUnitKind::SystemHeader => modules::generate_sys_module_cmd( - model, - cache, - transient::Downcast::downcast_ref::(tu_with_erased_type).unwrap(), - ), - _ => todo!(), + TranslationUnitKind::SystemHeader => { + let resolved_tu = + transient::Downcast::downcast_ref::(tu_with_erased_type) + .with_context(|| helpers::wrong_downcast_msg(translation_unit))?; + modules::generate_sys_module_cmd(model, cache, resolved_tu) + } + _ => (), } }; + + Ok(()) } /// Command line arguments generators procedures for C++ standard modules @@ -725,4 +729,12 @@ mod helpers { _ => TranslationUnitStatus::PendingToBuild, } } + + pub(crate) fn wrong_downcast_msg<'a, T: TranslationUnit<'a>>(translation_unit: &T) -> String { + format!( + "{}: {:?}", + error_messages::WRONG_DOWNCAST_FOR, + translation_unit.path() + ) + } } diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index 2affeb54..2cf1bada 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -44,6 +44,16 @@ pub mod error_messages { trigger them. The unique remaining, is ::New, that is already processed\ at the very beginning"; + pub const FAILURE_MODULE_INTERFACES: &str = + "An error happened while generating the commands for the module interfaces"; + pub const FAILURE_MODULE_IMPLEMENTATIONS: &str = + "An error happened while generating the commands for the module implementations"; + pub const FAILURE_TARGET_SOURCES: &str = + "An error happened while generating the commands for the declared sources of the target"; + pub const FAILURE_SYSTEM_MODULES: &str = + "An error happened while generating the commands for the declared system headers as modules"; + pub const WRONG_DOWNCAST_FOR: &str = "An error happened while resolving the original type of"; + pub mod msvc { pub const STDLIB_MODULES_NOT_FOUND: &str = "Can't find the MSVC standard library modules. Did you installed them?"; From 2170ee1c2273df6bbfed10187fa8194663dc785f Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 15 Jul 2024 09:23:55 +0200 Subject: [PATCH 41/73] fix: removing the trailing whitespace when printing the generated arguments for a translation unit --- zork++/src/lib/cli/output/arguments.rs | 10 +++++++--- zork++/src/lib/compiler/data_factory.rs | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index 53ffba6a..384a7302 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -107,9 +107,13 @@ pub struct Arguments<'a>(Vec>); impl<'a> core::fmt::Display for Arguments<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.iter().try_for_each(|arg| write!(f, "{} ", arg)) - // TODO: there's an ugly space at the end of every command line when Display is invoked - // :) Just fix it + let args = &mut self.0.iter(); + let mut idx = 0; + while idx < args.len() - 1 { + args.try_for_each(|arg| write!(f, "{} ", arg))?; + idx += 1; + } + args.try_for_each(|arg| write!(f, "{} ", arg)) } } diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index 6961fa46..fb8bd7b6 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -52,7 +52,7 @@ impl<'a> IntoIterator for CommonArgs<'a> { } /// Factory function for bring the data structure that holds the common arguments of a source -/// command line for every translation unit, regardeless the underlying choosen compiler +/// command line for every translation unit, regardless the underlying chosen compiler pub fn compiler_common_arguments_factory( model: &ZorkModel<'_>, cache: &ZorkCache<'_>, From e5998ca08d072134b2003705c1d0d87746132abd Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 16 Jul 2024 20:06:19 +0200 Subject: [PATCH 42/73] perf: aggressive optimizations of the compile commands generation --- zork++/src/lib/cache/compile_commands.rs | 80 ++++++++----- zork++/src/lib/cache/mod.rs | 141 ++++++++++++++++++----- zork++/src/lib/cli/output/arguments.rs | 8 +- zork++/src/lib/cli/output/commands.rs | 91 ++++++++++----- zork++/src/lib/compiler/mod.rs | 10 +- zork++/src/lib/lib.rs | 28 ++--- zork++/src/lib/project_model/mod.rs | 67 +++-------- zork++/src/lib/utils/constants.rs | 5 + zork++/src/lib/utils/fs.rs | 5 +- zork++/src/lib/utils/reader.rs | 2 +- 10 files changed, 268 insertions(+), 169 deletions(-) diff --git a/zork++/src/lib/cache/compile_commands.rs b/zork++/src/lib/cache/compile_commands.rs index 4a2cef26..75a70604 100644 --- a/zork++/src/lib/cache/compile_commands.rs +++ b/zork++/src/lib/cache/compile_commands.rs @@ -1,9 +1,11 @@ use crate::cache::ZorkCache; -use crate::cli::output::arguments::Arguments; -use crate::cli::output::commands::SourceCommandLine; +use crate::cli::output::arguments::{Argument}; + +use crate::project_model::compiler::CppCompiler; +use crate::project_model::ZorkModel; use crate::utils; -use crate::utils::constants::COMPILATION_DATABASE; -use color_eyre::eyre::{Context, Result}; +use crate::utils::constants::{error_messages, COMPILATION_DATABASE}; +use color_eyre::eyre::{Context, ContextCompat, Result}; use serde::Serialize; use std::fs::File; use std::path::{Path, PathBuf}; @@ -14,16 +16,49 @@ pub type CompileCommands<'a> = Vec>; /// for some static analysis external tools, like `clang-tidy`, and populates it with /// the generated commands for the translation units pub(crate) fn map_generated_commands_to_compilation_db<'a>( - cache: &'a ZorkCache<'a>, -) -> Result> { - log::trace!("Generating the compilation database..."); + program_data: &ZorkModel, + cache: &mut ZorkCache<'a>, +) -> Result<()> { + log::debug!("Generating the compilation database..."); let generated_commands = cache.get_all_commands_iter(); let mut compilation_db_entries: Vec = - Vec::with_capacity(cache.count_total_generated_commands()); // Without the linker one + Vec::with_capacity(cache.count_total_generated_commands()); + + let general_args = cache + .generated_commands + .general_args + .as_ref() + .expect(error_messages::GENERAL_ARGS_NOT_FOUND) + .get_args(); + + let compiler_specific_shared_args = cache + .generated_commands + .compiler_common_args + .as_ref() + .with_context(|| error_messages::COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND)? + .get_args(); + + let compile_but_dont_link: [Argument; 1] = + [Argument::from(match program_data.compiler.cpp_compiler { + CppCompiler::CLANG | CppCompiler::GCC => "-c", + CppCompiler::MSVC => "/c", + })]; + + for source_command_line in generated_commands { + let translation_unit_cmd_args = general_args + .iter() + .chain(compiler_specific_shared_args.iter()) + .chain(&compile_but_dont_link) + .chain(source_command_line.args.iter()) + .collect::>(); - for command in generated_commands { - compilation_db_entries.push(CompileCommand::from(command)); + let compile_command = CompileCommand { + directory: &source_command_line.directory, + file: &source_command_line.filename, + arguments: translation_unit_cmd_args, + }; + compilation_db_entries.push(compile_command); } let compile_commands_path = Path::new(COMPILATION_DATABASE); @@ -32,28 +67,15 @@ pub(crate) fn map_generated_commands_to_compilation_db<'a>( .with_context(|| "Error creating the compilation database")?; } - utils::fs::serialize_object_to_file(Path::new(compile_commands_path), &compilation_db_entries) - .with_context(move || "Error saving the compilation database")?; - - Ok(compilation_db_entries) + utils::fs::save_file(Path::new(compile_commands_path), &compilation_db_entries) + .with_context(move || "Error saving the compilation database") } /// Data model for serialize the data that will be outputted /// to the `compile_commands.json` compilation database file -#[derive(Serialize, Debug, Default, Clone)] +#[derive(Serialize, Debug)] pub struct CompileCommand<'a> { - pub directory: PathBuf, - pub file: String, - pub arguments: Arguments<'a>, -} - -impl<'a> From<&SourceCommandLine<'a>> for CompileCommand<'a> { - fn from(value: &SourceCommandLine<'a>) -> Self { - let value = value.clone(); - Self { - directory: value.directory, - file: value.filename, - arguments: value.args, - } - } + pub directory: &'a PathBuf, + pub file: &'a String, + pub arguments: Vec<&'a Argument<'a>>, } diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 18a75968..067ab04d 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -12,23 +12,26 @@ use std::{ fs, fs::File, path::{Path, PathBuf}, + time::Instant, }; use crate::config_file::ZorkConfigFile; use crate::domain::translation_unit::{TranslationUnit, TranslationUnitKind}; use crate::project_model::sourceset::SourceFile; -use crate::utils::constants::CACHE_FILE_EXT; +use crate::utils::constants::{dir_names, error_messages}; use crate::{ cli::{ input::CliArgs, output::commands::{Commands, SourceCommandLine}, }, + project_model, project_model::{compiler::CppCompiler, ZorkModel}, utils::{self}, }; use serde::{Deserialize, Serialize}; use crate::project_model::compiler::StdLibMode; +use crate::utils::constants; /// Standalone utility for load from the file system the Zork++ cache file /// for the target [`CppCompiler`] @@ -43,27 +46,35 @@ pub fn load<'a>( .build .as_ref() .and_then(|build_attr| build_attr.output_dir) - .unwrap_or("out"), + .unwrap_or(dir_names::DEFAULT_OUTPUT_DIR), ); - let cache_path = &output_dir.join("zork").join("cache"); + let cache_path = output_dir.join(constants::ZORK).join(dir_names::CACHE); let cache_file_path = cache_path .join(compiler.as_ref()) - .with_extension(CACHE_FILE_EXT); + .with_extension(constants::CACHE_FILE_EXT); - // TODO: analyze if the clear cache must be performed by target and/or active cfg file(s) // TODO: should we just have a cache dir with the __.json or similar? // Or just ...//_.json let cache = if !cache_file_path.exists() { - File::create(&cache_file_path).with_context(|| "Error creating the cache file")?; - helpers::initialize_default_cache(cache_file_path, compiler, &output_dir)? + File::create(&cache_file_path).with_context(|| error_messages::FAILURE_LOADING_CACHE)?; + helpers::initialize_cache(cache_path, cache_file_path, compiler, &output_dir)? } else if cache_path.exists() && cli_args.clear_cache { - fs::remove_dir_all(cache_path).with_context(|| "Error cleaning the Zork++ cache")?; - fs::create_dir(cache_path) - .with_context(|| "Error creating the cache subdirectory for {compiler}")?; - File::create(&cache_file_path) - .with_context(|| "Error creating the cache file after cleaning the cache")?; - helpers::initialize_default_cache(cache_file_path, compiler, &output_dir)? + fs::remove_dir_all(&cache_path).with_context(|| error_messages::FAILURE_CLEANING_CACHE)?; + fs::create_dir(&cache_path).with_context(|| { + format!( + "{} for: {}", + error_messages::FAILURE_CREATING_COMPILER_CACHE_DIR, + compiler + ) + })?; + File::create(&cache_file_path).with_context(|| { + format!( + "{} after cleaning the cache", + error_messages::FAILURE_LOADING_CACHE + ) + })?; + helpers::initialize_cache(cache_path, cache_file_path, compiler, &output_dir)? } else { log::trace!( "Loading Zork++ cache file for {compiler} at: {:?}", @@ -84,12 +95,12 @@ pub struct ZorkCache<'a> { } impl<'a> ZorkCache<'a> { - pub fn save(&mut self, program_data: &ZorkModel<'_>) -> Result<()> { - self.run_final_tasks(program_data)?; + pub fn save(&mut self, program_data: &ZorkModel<'_>, cli_args: &CliArgs) -> Result<()> { + self.run_final_tasks(program_data, cli_args)?; self.metadata.last_program_execution = Utc::now(); - utils::fs::serialize_object_to_file(&self.metadata.cache_file_path, self) - .with_context(move || "Error saving data to the Zork++ cache") + utils::fs::save_file(&self.metadata.cache_file_path, self) + .with_context(|| error_messages::FAILURE_SAVING_CACHE) } pub fn get_cmd_for_translation_unit_kind>( @@ -191,9 +202,27 @@ impl<'a> ZorkCache<'a> { } /// Runs the tasks just before end the program and save the cache - fn run_final_tasks(&mut self, program_data: &ZorkModel<'_>) -> Result<()> { - if program_data.project.compilation_db && self.metadata.regenerate_compilation_database { - compile_commands::map_generated_commands_to_compilation_db(self)?; + fn run_final_tasks(&mut self, program_data: &ZorkModel<'_>, cli_args: &CliArgs) -> Result<()> { + let process_removals = Instant::now(); + let deletions_on_cfg = helpers::check_user_files_removals(self, program_data, cli_args); + log::debug!( + "Zork++ took a total of {:?} ms on checking and process removed items", + process_removals.elapsed().as_millis() + ); + + if self.metadata.save_project_model { + project_model::save(program_data, self)?; + } + + if program_data.project.compilation_db + && (self.metadata.generate_compilation_database || deletions_on_cfg) + { + let compile_commands_time = Instant::now(); + compile_commands::map_generated_commands_to_compilation_db(program_data, self)?; + log::debug!( + "Zork++ took a total of {:?} ms on generate the compilation database", + compile_commands_time.elapsed().as_millis() + ); } Ok(()) @@ -244,8 +273,11 @@ pub struct CacheMetadata { pub process_no: i32, pub last_program_execution: DateTime, pub cache_file_path: PathBuf, + pub project_model_file_path: PathBuf, + #[serde(skip)] + pub generate_compilation_database: bool, #[serde(skip)] - pub regenerate_compilation_database: bool, + pub save_project_model: bool, } /// Type alias for the underlying key-value based collection of environmental variables @@ -411,28 +443,79 @@ mod helpers { use self::utils::constants::error_messages; use super::*; + use crate::cli::input::Command; + use crate::cli::output::commands::TranslationUnitStatus; use std::path::PathBuf; - pub(crate) fn initialize_default_cache<'a>( + pub(crate) fn initialize_cache<'a>( + cache_path: PathBuf, cache_file_path: PathBuf, compiler: CppCompiler, output_dir: &Path, ) -> Result> { - let mut default_initialized = ZorkCache { + let project_model_file_path = cache_path + .join(format!("{}_pm", compiler.as_ref())) + .with_extension(constants::CACHE_FILE_EXT); + + let mut cache = ZorkCache { metadata: CacheMetadata { - cache_file_path: cache_file_path.clone(), + cache_file_path, + project_model_file_path, ..Default::default() }, ..Default::default() }; - utils::fs::serialize_object_to_file(&cache_file_path, &default_initialized) - .with_context(|| error_messages::FAILURE_SAVING_CACHE)?; - - default_initialized + cache .run_tasks(compiler, output_dir) .with_context(|| error_messages::FAILURE_LOADING_INITIAL_CACHE_DATA)?; - Ok(default_initialized) + Ok(cache) + } + + /// Checks for those translation units that the process detected that must be deleted from the + /// cache -> [`TranslationUnitStatus::ToDelete`] or if the file has been removed from the + /// Zork++ configuration file or if it has been removed from the fs + /// + /// Can we only call this when we know that the user modified the ZorkCache file for the current iteration? + pub(crate) fn check_user_files_removals( + cache: &mut ZorkCache, + program_data: &ZorkModel<'_>, + cli_args: &CliArgs, + ) -> bool { + remove_if_needed_from_cache_and_count_changes( + &mut cache.generated_commands.interfaces, + &program_data.modules.interfaces, + ) || remove_if_needed_from_cache_and_count_changes( + &mut cache.generated_commands.implementations, + &program_data.modules.implementations, + ) || remove_if_needed_from_cache_and_count_changes( + &mut cache.generated_commands.sources, + if !cli_args.command.eq(&Command::Test) { + &program_data.executable.sourceset.sources + } else { + &program_data.tests.sourceset.sources + }, + ) || remove_if_needed_from_cache_and_count_changes( + &mut cache.generated_commands.system_modules, + &program_data.modules.sys_modules, + ) + } + + fn remove_if_needed_from_cache_and_count_changes<'a, T: TranslationUnit<'a>>( + cached_commands: &mut Vec, + user_declared_translation_units: &[T], + ) -> bool { + let removal_conditions = |scl: &SourceCommandLine| { + scl.status.eq(&TranslationUnitStatus::ToDelete) + || user_declared_translation_units + .iter() + .any(|cc: &T| cc.path().eq(&scl.path())) + }; + + let total_cached_source_command_lines = cached_commands.len(); + cached_commands.retain(removal_conditions); + + total_cached_source_command_lines > cached_commands.len() } } diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index 384a7302..00301e57 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -107,13 +107,7 @@ pub struct Arguments<'a>(Vec>); impl<'a> core::fmt::Display for Arguments<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let args = &mut self.0.iter(); - let mut idx = 0; - while idx < args.len() - 1 { - args.try_for_each(|arg| write!(f, "{} ", arg))?; - idx += 1; - } - args.try_for_each(|arg| write!(f, "{} ", arg)) + self.0.iter().try_for_each(|arg| write!(f, "{} ", arg)) } } diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index 111bbe45..dc0e1e21 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -52,22 +52,16 @@ pub fn run_generated_commands<'a>( CppCompiler::GCC => &cache.compilers_metadata.gcc.env_vars, }; - let translation_units = generated_commands - .cpp_stdlib - .as_mut_slice() - .iter_mut() - .chain(generated_commands.c_compat_stdlib.as_mut_slice().iter_mut()) - .chain(generated_commands.system_modules.as_mut_slice().iter_mut()) - .chain(generated_commands.interfaces.as_mut_slice().iter_mut()) - .chain(generated_commands.implementations.as_mut_slice().iter_mut()) - .chain(generated_commands.sources.as_mut_slice().iter_mut()) - .filter(|scl| scl.status.eq(&TranslationUnitStatus::PendingToBuild)) - .collect::>(); - - // let translation_units = generated_commands // TODO: how can I borrow twice generated_commands? - // .get_all_command_lines() - // .filter(|scl| scl.status.eq(&TranslationUnitStatus::PendingToBuild)) - // .collect::>(); + let translation_units_commands: Vec<&mut SourceCommandLine> = + helpers::get_translation_units_commands( + // Independent borrows to avoid have borrow checker yielding at me + &mut generated_commands.cpp_stdlib, + &mut generated_commands.c_compat_stdlib, + &mut generated_commands.system_modules, + &mut generated_commands.interfaces, + &mut generated_commands.implementations, + &mut generated_commands.sources, + ); let compile_but_dont_link: [Argument; 1] = [Argument::from(match program_data.compiler.cpp_compiler { @@ -75,7 +69,7 @@ pub fn run_generated_commands<'a>( CppCompiler::MSVC => "/c", })]; - for translation_unit_cmd in translation_units { + for translation_unit_cmd in translation_units_commands { // Join the concrete args of any translation unit with the ones held in the flyweights let translation_unit_cmd_args: Arguments = general_args .iter() @@ -88,7 +82,6 @@ pub fn run_generated_commands<'a>( translation_unit_cmd.status = TranslationUnitStatus::from(&r); if let Err(e) = r { - cache.save(program_data)?; return Err(e); } else if !r.as_ref().unwrap().success() { let err = eyre!( @@ -100,22 +93,13 @@ pub fn run_generated_commands<'a>( } log::info!("Processing the linker command line..."); - let r = execute_command( + let r = helpers::execute_linker_command_line( program_data, - &general_args - .iter() - .chain(compiler_specific_shared_args.iter()) - .chain( - generated_commands - .linker - .get_target_output_for(program_data.compiler.cpp_compiler) - .iter(), - ) - .chain(generated_commands.linker.byproducts.iter()) - .collect::(), + general_args, + compiler_specific_shared_args, + &generated_commands.linker, env_vars, ); - cache.generated_commands.linker.execution_result = TranslationUnitStatus::from(&r); if let Err(e) = r { @@ -319,11 +303,34 @@ impl From<&Result> for TranslationUnitStatus { mod helpers { - use crate::cli::output::commands::TranslationUnitStatus; + use crate::cli::output::commands::{ + execute_command, LinkerCommandLine, SourceCommandLine, TranslationUnitStatus, + }; + use crate::cache::EnvVars; + use crate::cli::output::arguments::Arguments; + use crate::project_model::ZorkModel; use color_eyre::eyre::Result; use std::process::ExitStatus; + pub(crate) fn execute_linker_command_line( + program_data: &ZorkModel, + general_args: Arguments, + compiler_specific_shared_args: Arguments, + linker_command_line: &LinkerCommandLine, + env_vars: &EnvVars, + ) -> Result { + let linker_args = + linker_command_line.get_target_output_for(program_data.compiler.cpp_compiler); + let args = general_args + .iter() + .chain(linker_args.iter()) + .chain(compiler_specific_shared_args.iter()) + .chain(linker_command_line.byproducts.iter()) + .collect::(); + execute_command(program_data, &args, env_vars) + } + /// Convenient way of handle a command execution result avoiding duplicate code pub(crate) fn handle_command_execution_result( value: &Result, @@ -339,4 +346,24 @@ mod helpers { Err(_) => TranslationUnitStatus::Error, } } + + pub(crate) fn get_translation_units_commands<'a, 'b>( + cpp_stdlib: &'b mut Option>, + c_compat_stdlib: &'b mut Option>, + system_modules: &'b mut Vec>, + interfaces: &'b mut Vec>, + implementations: &'b mut Vec>, + sources: &'b mut Vec>, + ) -> Vec<&'b mut SourceCommandLine<'a>> { + cpp_stdlib + .as_mut_slice() + .iter_mut() + .chain(c_compat_stdlib.as_mut_slice().iter_mut()) + .chain(system_modules.as_mut_slice().iter_mut()) + .chain(interfaces.as_mut_slice().iter_mut()) + .chain(implementations.as_mut_slice().iter_mut()) + .chain(sources.as_mut_slice().iter_mut()) + .filter(|scl| scl.status.eq(&TranslationUnitStatus::PendingToBuild)) + .collect::>() + } } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 474d2fa3..b7655013 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -272,6 +272,7 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( generated_cmd.status = build_translation_unit; } else { + cache.metadata.generate_compilation_database = true; let tu_with_erased_type = translation_unit.as_any(); match &for_kind { @@ -327,6 +328,7 @@ mod modules { ModuleImplementationModel, ModuleInterfaceModel, SystemModule, }; use crate::project_model::ZorkModel; + use crate::utils::constants::dir_names; /// Generates the expected arguments for precompile the BMIs depending on self pub fn generate_module_interface_cmd<'a>( @@ -361,8 +363,8 @@ mod modules { arguments.push("/ifcOutput"); let implicit_lookup_mius_path = out_dir .join(compiler.as_ref()) - .join("modules") - .join("interfaces"); + .join(dir_names::MODULES) + .join(dir_names::INTERFACES); arguments.push(implicit_lookup_mius_path); // The output .obj file @@ -614,8 +616,8 @@ mod helpers { ) -> PathBuf { out_dir .join(compiler.as_ref()) - .join("modules") - .join("interfaces") + .join(dir_names::MODULES) + .join(dir_names::INTERFACES) .join(format!( "{module_name}.{}", if compiler.eq(&CppCompiler::MSVC) { diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 96027fe8..e430f1be 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -18,7 +18,7 @@ pub mod worker { use crate::config_file::ZorkConfigFile; use crate::project_model; use crate::{config_file, utils::fs::get_project_root_absolute_path}; - use std::{fs, path::Path}; + use std::{fs, path::Path, time::Instant}; use crate::utils::constants::{dir_names, error_messages, ZORK}; use crate::{ @@ -81,13 +81,16 @@ pub mod worker { create_output_directory(&config, &abs_project_root)?; - let cache: ZorkCache<'_> = cache::load(&config, cli_args, &abs_project_root)?; + let mut cache: ZorkCache<'_> = cache::load(&config, cli_args, &abs_project_root)?; let program_data: ZorkModel<'_> = if config_file.last_time_modified > cache.metadata.last_program_execution { + log::debug!("Rebuilding the ZorkModel due to changes of the cfg file"); + cache.metadata.save_project_model = true; utils::reader::build_model(config, cli_args, &abs_project_root)? } else { - project_model::load(config, cli_args, &abs_project_root)? + log::debug!("Loading the ZorkModel from the cache"); + project_model::load(&cache)? }; do_main_work_based_on_cli_input(cli_args, &program_data, cache).with_context(|| { @@ -112,17 +115,16 @@ pub mod worker { program_data: &'a ZorkModel<'a>, mut cache: ZorkCache<'a>, ) -> Result<()> { - // TODO: if we split the line below, we can only check for changes on the modified - // files IF and only IF the configuration files has been modified - // so we will have the need_to_rebuild in other place before the commands generation - // one option is directly on the reader, by just checking it's modification datetime (for the tu) - // - // other is to have just a separate function that only passes the required data - // like cache to be modified and the new ones - // - // TODO: introduce debug times for the main processes + let generate_commands_ts = Instant::now(); + // Generates the commands for every translation unit and/or checks on successive iterations + // of the program it any of them has been modified so the files must be marked to be + // rebuilt again generate_commands(program_data, &mut cache, cli_args) .with_context(|| error_messages::FAILURE_GENERATING_COMMANDS)?; + log::debug!( + "Zork++ took a total of {:?} ms on handling the generated commands", + generate_commands_ts.elapsed().as_millis() + ); let execution_result = match cli_args.command { Command::Build => commands::run_generated_commands(program_data, &mut cache), @@ -139,7 +141,7 @@ pub mod worker { _ => todo!("{}", error_messages::CLI_ARGS_CMD_NEW_BRANCH), }; - cache.save(program_data)?; + cache.save(program_data, cli_args)?; execution_result } diff --git a/zork++/src/lib/project_model/mod.rs b/zork++/src/lib/project_model/mod.rs index 0dd25974..6a0e968a 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -6,29 +6,19 @@ pub mod project; pub mod sourceset; pub mod tests; -use std::{fmt::Debug, path::Path}; +use std::fmt::Debug; use color_eyre::eyre::Context; use color_eyre::Result; use serde::{Deserialize, Serialize}; -use crate::{ - cli::input::CliArgs, - config_file::ZorkConfigFile, - utils::{ - self, - constants::{debug_messages, error_messages, CACHE_FILE_EXT}, - reader, - }, -}; +use crate::cache::ZorkCache; + +use crate::utils; use self::{ - build::BuildModel, - compiler::{CompilerModel, CppCompiler}, - executable::ExecutableModel, - modules::ModulesModel, - project::ProjectModel, - tests::TestsModel, + build::BuildModel, compiler::CompilerModel, executable::ExecutableModel, modules::ModulesModel, + project::ProjectModel, tests::TestsModel, }; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] @@ -41,41 +31,14 @@ pub struct ZorkModel<'a> { pub tests: TestsModel<'a>, } -/// Loads the mapped [`ZorkModel`] for a concrete [`ZorkConfigFile`] if a save file exists, -/// otherwise, calls the mapping processor to load the data from the configuration file -pub fn load<'a>( - config: ZorkConfigFile<'a>, - cli_args: &'a CliArgs, - absolute_project_root: &Path, -) -> Result> { - let compiler: CppCompiler = config.compiler.cpp_compiler.into(); - let cache_path = Path::new(absolute_project_root) - .join( - config - .build - .as_ref() - .and_then(|build_attr| build_attr.output_dir) - .unwrap_or("out"), - ) - .join("zork") - .join("cache"); - - let cached_project_model_path = cache_path - .join(format!("{}_pm", compiler.as_ref())) - .with_extension(CACHE_FILE_EXT); - - utils::fs::load_and_deserialize::(&cached_project_model_path) - .or_else(|_| { - log::debug!("{}", debug_messages::MAPPING_CFG_TO_MODEL); - let program_data: ZorkModel = - reader::build_model(config, cli_args, absolute_project_root)?; - utils::fs::serialize_object_to_file::( - &cached_project_model_path, - &program_data, - ) - .with_context(|| error_messages::PROJECT_MODEL_SAVE)?; - - Ok::, color_eyre::eyre::Error>(program_data) - }) +/// Loads the mapped [`ZorkModel`] for a concrete [`ZorkConfigFile`] from the [`ZorkCache`] +pub fn load<'a>(cache: &ZorkCache<'a>) -> Result> { + utils::fs::load_and_deserialize::(&cache.metadata.project_model_file_path) .with_context(|| "Error loading the project model") } + +/// Saves the mapped [`ZorkModel`] for a concrete [`ZorkConfigFile`] on the [`ZorkCache`] +pub fn save(program_data: &ZorkModel, cache: &ZorkCache) -> Result<()> { + utils::fs::save_file(&cache.metadata.project_model_file_path, program_data) + .with_context(|| "Error saving the project model") +} diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index 2cf1bada..288ea6f0 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -29,7 +29,12 @@ pub mod error_messages { pub const FAILURE_GENERATING_COMMANDS: &str = "Failed to generated the commands for the project"; pub const FAILED_BUILD_FOR_CFG_FILE: &str = "Failed to build the project for the config file"; + pub const FAILURE_CREATING_CACHE_FILE: &str = "Error creating the cache file"; + pub const FAILURE_CREATING_COMPILER_CACHE_DIR: &str = + "Error creating the cache subdirectory for compiler"; + pub const FAILURE_LOADING_CACHE: &str = "Failed to load the Zork++ cache"; pub const FAILURE_LOADING_INITIAL_CACHE_DATA: &str = "Failed to load the cache initial data"; + pub const FAILURE_CLEANING_CACHE: &str = "Error cleaning the Zork++ cache"; pub const FAILURE_SAVING_CACHE: &str = "Error saving data to the Zork++ cache"; pub const GENERAL_ARGS_NOT_FOUND: &str = "Something went wrong loading the general arguments"; pub const PROJECT_MODEL_MAPPING: &str = "Error building the project model"; diff --git a/zork++/src/lib/utils/fs.rs b/zork++/src/lib/utils/fs.rs index 067b1ebf..ca16d74c 100644 --- a/zork++/src/lib/utils/fs.rs +++ b/zork++/src/lib/utils/fs.rs @@ -83,12 +83,12 @@ pub fn get_file_details>(p: P) -> Result<(PathBuf, String, String )) } -pub fn serialize_object_to_file(path: &Path, data: &T) -> Result<()> +pub fn save_file(path: &Path, data: &T) -> Result<()> where T: Serialize + ?Sized, { serde_json::to_writer_pretty( - File::create(path).with_context(|| "Error opening the cache file")?, + File::create(path).with_context(|| format!("Error opening file: {:?}", path))?, data, ) .with_context(|| "Error serializing data to the cache") @@ -102,6 +102,7 @@ where let buffer = BufReader::new( File::open(path.as_ref()).with_context(|| format!("Error opening {:?}", path))?, ); + // TODO: remove the panic, use the default Ok(serde_json::from_reader(buffer) .unwrap_or_else(|_| panic!("Unable to parse file: {:?}", path))) } diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index c7e3ced9..57f63dff 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -48,7 +48,7 @@ pub struct ConfigFile { /// Checks for the existence of the `zork_.toml` configuration files /// present in the same directory when the binary is called, and /// returns a collection of the ones found. -// +/// /// *base_path* - A parameter for receive an input via command line /// parameter to indicate where the configuration files lives in /// the client's project. Defaults to `.` From 126687545dbacb90b7287d4a6ed2c47ccb9f1e44 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 16 Jul 2024 20:11:03 +0200 Subject: [PATCH 43/73] fix: cleaned needless lifetimes --- zork++/src/lib/cache/compile_commands.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zork++/src/lib/cache/compile_commands.rs b/zork++/src/lib/cache/compile_commands.rs index 75a70604..0cd6b77f 100644 --- a/zork++/src/lib/cache/compile_commands.rs +++ b/zork++/src/lib/cache/compile_commands.rs @@ -15,9 +15,9 @@ pub type CompileCommands<'a> = Vec>; /// Generates the `compile_commands.json` file, that acts as a compilation database /// for some static analysis external tools, like `clang-tidy`, and populates it with /// the generated commands for the translation units -pub(crate) fn map_generated_commands_to_compilation_db<'a>( +pub(crate) fn map_generated_commands_to_compilation_db( program_data: &ZorkModel, - cache: &mut ZorkCache<'a>, + cache: &mut ZorkCache, ) -> Result<()> { log::debug!("Generating the compilation database..."); From bb41e1e6737ceb0cac9f731da7f4539ed0ec54e2 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 16 Jul 2024 20:15:33 +0200 Subject: [PATCH 44/73] chore: cargo fmt --- zork++/test/test.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zork++/test/test.rs b/zork++/test/test.rs index 871fd5b0..5d4efc1f 100644 --- a/zork++/test/test.rs +++ b/zork++/test/test.rs @@ -35,8 +35,8 @@ fn test_clang_full_process() -> Result<()> { "-vv", "--root", &project_root, - "--driver-path", - "clang++-16", // Local cfg issues + /* "--driver-path", + "clang++-16", // Local cfg issues*/ "run", ])); assert!(process_result.is_ok(), "{}", process_result.unwrap_err()); From 6fbfd74cb3c5a866eadfae95f985afb8f3acce74 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 16 Jul 2024 20:16:00 +0200 Subject: [PATCH 45/73] chore: cargo fmt --- zork++/src/lib/cache/compile_commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zork++/src/lib/cache/compile_commands.rs b/zork++/src/lib/cache/compile_commands.rs index 0cd6b77f..ee02dd7f 100644 --- a/zork++/src/lib/cache/compile_commands.rs +++ b/zork++/src/lib/cache/compile_commands.rs @@ -1,5 +1,5 @@ use crate::cache::ZorkCache; -use crate::cli::output::arguments::{Argument}; +use crate::cli::output::arguments::Argument; use crate::project_model::compiler::CppCompiler; use crate::project_model::ZorkModel; From 3049251176c5f525c2db2875ce36b354535471ce Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 16 Jul 2024 20:28:03 +0200 Subject: [PATCH 46/73] fix: changed the unwrap_or_else of the load_and_deserialize for its original behaviour (unwrap or default) --- zork++/src/lib/utils/fs.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/zork++/src/lib/utils/fs.rs b/zork++/src/lib/utils/fs.rs index ca16d74c..9b792bbe 100644 --- a/zork++/src/lib/utils/fs.rs +++ b/zork++/src/lib/utils/fs.rs @@ -102,7 +102,6 @@ where let buffer = BufReader::new( File::open(path.as_ref()).with_context(|| format!("Error opening {:?}", path))?, ); - // TODO: remove the panic, use the default - Ok(serde_json::from_reader(buffer) - .unwrap_or_else(|_| panic!("Unable to parse file: {:?}", path))) + + Ok(serde_json::from_reader(buffer).unwrap_or_default()) } From 3879b358facb85a32e746f0a98f74e0c3228a6c9 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 19 Jul 2024 16:48:55 +0200 Subject: [PATCH 47/73] chore: conceptually reordering the data structures of the generated commands within the domain on the project files --- zork++/Cargo.lock | 8 + zork++/Cargo.toml | 1 + zork++/src/lib/cache/compile_commands.rs | 2 +- zork++/src/lib/cache/mod.rs | 27 ++- .../cli/output/{commands.rs => executors.rs} | 172 +----------------- zork++/src/lib/cli/output/mod.rs | 3 +- zork++/src/lib/compiler/data_factory.rs | 2 +- zork++/src/lib/compiler/mod.rs | 35 ++-- .../output => domain/commands}/arguments.rs | 7 +- .../src/lib/domain/commands/command_lines.rs | 107 +++++++++++ zork++/src/lib/domain/commands/mod.rs | 2 + zork++/src/lib/domain/mod.rs | 1 + zork++/src/lib/domain/target.rs | 3 +- zork++/src/lib/domain/translation_unit.rs | 76 +++++++- zork++/src/lib/lib.rs | 10 +- zork++/src/lib/project_model/compiler.rs | 2 +- zork++/src/lib/project_model/executable.rs | 6 +- zork++/src/lib/project_model/sourceset.rs | 2 +- zork++/src/lib/project_model/tests.rs | 6 +- zork++/src/lib/utils/reader.rs | 2 +- 20 files changed, 244 insertions(+), 230 deletions(-) rename zork++/src/lib/cli/output/{commands.rs => executors.rs} (53%) rename zork++/src/lib/{cli/output => domain/commands}/arguments.rs (98%) create mode 100644 zork++/src/lib/domain/commands/command_lines.rs create mode 100644 zork++/src/lib/domain/commands/mod.rs diff --git a/zork++/Cargo.lock b/zork++/Cargo.lock index 8fd9c1ae..25b7782a 100644 --- a/zork++/Cargo.lock +++ b/zork++/Cargo.lock @@ -863,6 +863,14 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_merge" +version = "0.1.0" +dependencies = [ + "serde", + "toml", +] + [[package]] name = "transient" version = "0.4.0" diff --git a/zork++/Cargo.toml b/zork++/Cargo.toml index 76a3e075..156450f6 100644 --- a/zork++/Cargo.toml +++ b/zork++/Cargo.toml @@ -1,3 +1,4 @@ +workspace = { members = ["merging_toml"] } [package] name = "zork" version = "0.9.0" diff --git a/zork++/src/lib/cache/compile_commands.rs b/zork++/src/lib/cache/compile_commands.rs index ee02dd7f..b1a68494 100644 --- a/zork++/src/lib/cache/compile_commands.rs +++ b/zork++/src/lib/cache/compile_commands.rs @@ -1,6 +1,6 @@ use crate::cache::ZorkCache; -use crate::cli::output::arguments::Argument; +use crate::domain::commands::arguments::Argument; use crate::project_model::compiler::CppCompiler; use crate::project_model::ZorkModel; use crate::utils; diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 067ab04d..4a7b1c8c 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -16,14 +16,12 @@ use std::{ }; use crate::config_file::ZorkConfigFile; +use crate::domain::commands::command_lines::{Commands, SourceCommandLine}; use crate::domain::translation_unit::{TranslationUnit, TranslationUnitKind}; use crate::project_model::sourceset::SourceFile; use crate::utils::constants::{dir_names, error_messages}; use crate::{ - cli::{ - input::CliArgs, - output::commands::{Commands, SourceCommandLine}, - }, + cli::input::CliArgs, project_model, project_model::{compiler::CppCompiler, ZorkModel}, utils::{self}, @@ -56,7 +54,7 @@ pub fn load<'a>( // TODO: should we just have a cache dir with the __.json or similar? // Or just ...//_.json - let cache = if !cache_file_path.exists() { + let mut cache = if !cache_file_path.exists() { File::create(&cache_file_path).with_context(|| error_messages::FAILURE_LOADING_CACHE)?; helpers::initialize_cache(cache_path, cache_file_path, compiler, &output_dir)? } else if cache_path.exists() && cli_args.clear_cache { @@ -84,6 +82,8 @@ pub fn load<'a>( .with_context(|| "Error loading the Zork++ cache")? }; + cache.metadata.process_no += 1; + Ok(cache) } @@ -230,6 +230,7 @@ impl<'a> ZorkCache<'a> { /// Method that returns the HashMap that holds the environmental variables that must be passed /// to the underlying shell + #[inline(always)] pub fn get_process_env_args(&'a mut self, compiler: CppCompiler) -> &'a EnvVars { match compiler { CppCompiler::MSVC => &self.compilers_metadata.msvc.env_vars, @@ -441,10 +442,9 @@ mod msvc { mod helpers { use self::utils::constants::error_messages; - use super::*; use crate::cli::input::Command; - use crate::cli::output::commands::TranslationUnitStatus; + use crate::domain::translation_unit::TranslationUnitStatus; use std::path::PathBuf; pub(crate) fn initialize_cache<'a>( @@ -507,14 +507,21 @@ mod helpers { user_declared_translation_units: &[T], ) -> bool { let removal_conditions = |scl: &SourceCommandLine| { - scl.status.eq(&TranslationUnitStatus::ToDelete) - || user_declared_translation_units + scl.status.eq(&TranslationUnitStatus::ToDelete) || { + let r = user_declared_translation_units .iter() - .any(|cc: &T| cc.path().eq(&scl.path())) + .any(|cc| cc.path().eq(&scl.path())); + + if !r { + log::debug!("Found translation_unit removed from cfg: {:?}", scl); + } + r + } }; let total_cached_source_command_lines = cached_commands.len(); cached_commands.retain(removal_conditions); + // TODO: remove them also from the linker total_cached_source_command_lines > cached_commands.len() } diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/executors.rs similarity index 53% rename from zork++/src/lib/cli/output/commands.rs rename to zork++/src/lib/cli/output/executors.rs index dc0e1e21..d26b0d88 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/executors.rs @@ -2,17 +2,12 @@ //! by Zork++ use std::ffi::OsStr; -use std::fmt::Debug; -use std::{ - path::{Path, PathBuf}, - process::ExitStatus, -}; +use std::{path::Path, process::ExitStatus}; -use super::arguments::Argument; use crate::cache::EnvVars; -use crate::cli::output::arguments::Arguments; -use crate::compiler::data_factory::{CommonArgs, CompilerCommonArguments}; -use crate::domain::translation_unit::TranslationUnit; +use crate::domain::commands::arguments::{Argument, Arguments}; +use crate::domain::commands::command_lines::SourceCommandLine; +use crate::domain::translation_unit::TranslationUnitStatus; use crate::utils::constants::error_messages; use crate::{ cache::ZorkCache, @@ -24,7 +19,6 @@ use color_eyre::{ eyre::{eyre, Context}, Report, Result, }; -use serde::{Deserialize, Serialize}; pub fn run_generated_commands<'a>( program_data: &ZorkModel<'a>, @@ -169,146 +163,12 @@ where .with_context(|| format!("[{compiler}] - Command {arguments} failed!")) } -/// Type for representing the command line that will be sent to the target compiler, and -/// store its different components -/// -/// * directory*: the path where the translation unit lives -/// * filename*: the translation unit declared name on the fs with the extension -/// * args*: member that holds all the cmd arguments that will be passed to the compiler driver -/// * status*: A [`TranslationUnitStatus`] that represents all the different phases that a source command -/// line can have among all the different iterations of the program, changing according to the modifications -/// over the translation unit in the fs and the result of the build execution -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SourceCommandLine<'a> { - pub directory: PathBuf, - pub filename: String, - pub args: Arguments<'a>, - pub status: TranslationUnitStatus, -} - -impl<'a> SourceCommandLine<'a> { - pub fn new>(tu: &T, args: Arguments<'a>) -> Self { - Self { - directory: PathBuf::from(tu.parent()), - filename: tu.filename(), - args, - status: TranslationUnitStatus::PendingToBuild, - } - } - - pub fn path(&self) -> PathBuf { - self.directory.join(Path::new(&self.filename)) - } - - pub fn filename(&self) -> &String { - &self.filename - } -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct LinkerCommandLine<'a> { - pub target: Argument<'a>, - pub byproducts: Arguments<'a>, - pub extra_args: Arguments<'a>, - pub execution_result: TranslationUnitStatus, -} - -impl<'a> LinkerCommandLine<'a> { - pub fn get_target_output_for(&self, compiler: CppCompiler) -> Vec { - match compiler { - CppCompiler::CLANG | CppCompiler::GCC => { - vec![Argument::from("-o"), self.target.clone()] - } - CppCompiler::MSVC => vec![self.target.clone()], - } - } - - /// Saves the path at which a compilation product of any translation unit will be placed, - /// in order to add it to the files that will be linked to generate the final product - /// in the two-phase compilation model - pub fn add_byproduct_path(&mut self, path: PathBuf) { - self.byproducts.push(path); - } -} - -/// Holds the generated command line arguments for a concrete compiler -#[derive(Serialize, Deserialize, Default, Debug)] -pub struct Commands<'a> { - pub general_args: Option>, - pub compiler_common_args: Option>, - pub cpp_stdlib: Option>, - pub c_compat_stdlib: Option>, - pub system_modules: Vec>, - pub interfaces: Vec>, - pub implementations: Vec>, - pub sources: Vec>, - pub linker: LinkerCommandLine<'a>, -} - -impl<'a> Commands<'a> { - /// Returns a [std::iter::Chain] (behind the opaque impl clause return type signature) - /// which points to all the generated commands for the two variants of the compilers vendors C++ modular - /// standard libraries implementations (see: [crate::project_model::compiler::StdLibMode]) - /// joined to all the commands generated for every [TranslationUnit] declared by the user for - /// its project - pub fn get_all_command_lines( - &mut self, - ) -> impl Iterator> + Debug { - self.cpp_stdlib - .as_mut_slice() - .iter_mut() - .chain(self.c_compat_stdlib.as_mut_slice().iter_mut()) - .chain(self.system_modules.as_mut_slice().iter_mut()) - .chain(self.interfaces.as_mut_slice().iter_mut()) - .chain(self.implementations.as_mut_slice().iter_mut()) - .chain(self.sources.as_mut_slice().iter_mut()) - } - - pub fn add_linker_file_path(&mut self, path: PathBuf) { - self.linker.add_byproduct_path(path); - } -} - -/// The different states of a translation unit in the whole lifecycle of -/// the build process and across different iterations of the same -#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq)] -pub enum TranslationUnitStatus { - /// A command that is executed correctly - Success, - /// A skipped command due to previous successful iterations - Cached, - /// A command which is return code indicates an unsuccessful execution - Failed, - /// Whenever a translation unit must be rebuilt - #[default] - PendingToBuild, - /// The associated [`TranslationUnit`] has been deleted from the user's configuration and therefore, - /// it should be removed from the cache as well as its generated byproducts - ToDelete, - /// The execution failed, returning a [`Result`] with the Err variant - Error, -} - -impl From> for TranslationUnitStatus { - fn from(value: Result) -> Self { - helpers::handle_command_execution_result(&value) - } -} - -impl From<&Result> for TranslationUnitStatus { - fn from(value: &Result) -> Self { - helpers::handle_command_execution_result(value) - } -} - mod helpers { - - use crate::cli::output::commands::{ - execute_command, LinkerCommandLine, SourceCommandLine, TranslationUnitStatus, - }; - use crate::cache::EnvVars; - use crate::cli::output::arguments::Arguments; + use crate::cli::output::executors::execute_command; + use crate::domain::commands::arguments::Arguments; + use crate::domain::commands::command_lines::{LinkerCommandLine, SourceCommandLine}; + use crate::domain::translation_unit::TranslationUnitStatus; use crate::project_model::ZorkModel; use color_eyre::eyre::Result; use std::process::ExitStatus; @@ -331,22 +191,6 @@ mod helpers { execute_command(program_data, &args, env_vars) } - /// Convenient way of handle a command execution result avoiding duplicate code - pub(crate) fn handle_command_execution_result( - value: &Result, - ) -> TranslationUnitStatus { - match value { - Ok(r) => { - if r.success() { - TranslationUnitStatus::Success - } else { - TranslationUnitStatus::Failed - } - } - Err(_) => TranslationUnitStatus::Error, - } - } - pub(crate) fn get_translation_units_commands<'a, 'b>( cpp_stdlib: &'b mut Option>, c_compat_stdlib: &'b mut Option>, diff --git a/zork++/src/lib/cli/output/mod.rs b/zork++/src/lib/cli/output/mod.rs index 2734db4e..42044def 100644 --- a/zork++/src/lib/cli/output/mod.rs +++ b/zork++/src/lib/cli/output/mod.rs @@ -1,3 +1,2 @@ //! Defines operations or types that are related with send data to a system shell -pub mod arguments; -pub mod commands; +pub mod executors; diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index fb8bd7b6..c75e3ca8 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -2,9 +2,9 @@ //! translation unit, having shared data without replicating it until the final command line must //! be generated in order to be stored (in cache) and executed (in the underlying shell) +use crate::domain::commands::arguments::{clang_args, Argument, Arguments}; use crate::{ cache::ZorkCache, - cli::output::arguments::{clang_args, Argument, Arguments}, domain::target::ExtraArgs, project_model::{ compiler::{CppCompiler, StdLib}, diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index b7655013..ee1f9f6e 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -8,15 +8,13 @@ use std::path::Path; use color_eyre::Result; -use crate::cli::output::commands::TranslationUnitStatus; +use crate::domain::commands::arguments::Argument; +use crate::domain::translation_unit::TranslationUnitStatus; use crate::project_model::modules::SystemModule; use crate::utils::constants::error_messages; use crate::{ cache::ZorkCache, - cli::{ - input::{CliArgs, Command}, - output::{arguments::Argument, commands::SourceCommandLine}, - }, + cli::input::{CliArgs, Command}, domain::{ target::ExecutableTarget, translation_unit::{TranslationUnit, TranslationUnitKind}, @@ -318,11 +316,11 @@ mod modules { use std::path::Path; use crate::cache::ZorkCache; - use crate::cli::output::arguments::{clang_args, msvc_args, Arguments}; - use crate::cli::output::commands::{SourceCommandLine, TranslationUnitStatus}; use crate::compiler::helpers; use crate::compiler::helpers::generate_bmi_file_path; - use crate::domain::translation_unit::TranslationUnit; + use crate::domain::commands::arguments::{clang_args, msvc_args, Arguments}; + use crate::domain::commands::command_lines::SourceCommandLine; + use crate::domain::translation_unit::{TranslationUnit, TranslationUnitStatus}; use crate::project_model::compiler::{CppCompiler, StdLibMode}; use crate::project_model::modules::{ ModuleImplementationModel, ModuleInterfaceModel, SystemModule, @@ -518,14 +516,12 @@ mod modules { /// Specific operations over source files mod sources { use crate::cache::ZorkCache; - use crate::cli::output::arguments::Arguments; + use crate::domain::commands::arguments::Arguments; + use crate::domain::commands::command_lines::SourceCommandLine; use crate::domain::target::ExecutableTarget; use crate::domain::translation_unit::TranslationUnit; use crate::project_model::sourceset::SourceFile; - use crate::{ - cli::output::commands::SourceCommandLine, - project_model::{compiler::CppCompiler, ZorkModel}, - }; + use crate::project_model::{compiler::CppCompiler, ZorkModel}; use super::helpers; @@ -561,15 +557,12 @@ mod sources { /// kind of workflow that should be done with this parse, format and /// generate. mod helpers { - use std::path::PathBuf; - - use chrono::{DateTime, Utc}; - - use crate::cli::output::commands::TranslationUnitStatus; - - use crate::utils::constants::dir_names; - use super::*; + use crate::domain::commands::command_lines::SourceCommandLine; + use crate::domain::translation_unit::TranslationUnitStatus; + use crate::utils::constants::dir_names; + use chrono::{DateTime, Utc}; + use std::path::PathBuf; /// Creates the path for a prebuilt module interface, based on the default expected /// extension for BMI's given a compiler diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/domain/commands/arguments.rs similarity index 98% rename from zork++/src/lib/cli/output/arguments.rs rename to zork++/src/lib/domain/commands/arguments.rs index 00301e57..bfe39065 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/domain/commands/arguments.rs @@ -267,11 +267,10 @@ pub mod clang_args { } pub mod msvc_args { + use crate::cache::ZorkCache; + use crate::domain::commands::command_lines::SourceCommandLine; use crate::domain::translation_unit::TranslationUnit; - use crate::{ - cache::ZorkCache, cli::output::commands::SourceCommandLine, - project_model::compiler::StdLibMode, - }; + use crate::project_model::compiler::StdLibMode; use super::Arguments; diff --git a/zork++/src/lib/domain/commands/command_lines.rs b/zork++/src/lib/domain/commands/command_lines.rs new file mode 100644 index 00000000..158736c8 --- /dev/null +++ b/zork++/src/lib/domain/commands/command_lines.rs @@ -0,0 +1,107 @@ +use crate::compiler::data_factory::{CommonArgs, CompilerCommonArguments}; +use crate::domain::commands::arguments::{Argument, Arguments}; +use crate::domain::translation_unit::{TranslationUnit, TranslationUnitStatus}; +use crate::project_model::compiler::CppCompiler; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use std::path::{Path, PathBuf}; + +/// Type for representing the command line that will be sent to the target compiler, and +/// store its different components +/// +/// * directory*: the path where the translation unit lives +/// * filename*: the translation unit declared name on the fs with the extension +/// * args*: member that holds all the cmd arguments that will be passed to the compiler driver +/// * status*: A [`TranslationUnitStatus`] that represents all the different phases that a source command +/// line can have among all the different iterations of the program, changing according to the modifications +/// over the translation unit in the fs and the result of the build execution +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SourceCommandLine<'a> { + pub directory: PathBuf, + pub filename: String, + pub args: Arguments<'a>, + pub status: TranslationUnitStatus, +} + +impl<'a> SourceCommandLine<'a> { + pub fn new>(tu: &T, args: Arguments<'a>) -> Self { + Self { + directory: PathBuf::from(tu.parent()), + filename: tu.filename(), + args, + status: TranslationUnitStatus::PendingToBuild, + } + } + + pub fn path(&self) -> PathBuf { + self.directory.join(Path::new(&self.filename)) + } + + pub fn filename(&self) -> &String { + &self.filename + } +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct LinkerCommandLine<'a> { + pub target: Argument<'a>, + pub byproducts: Arguments<'a>, + pub extra_args: Arguments<'a>, + pub execution_result: TranslationUnitStatus, +} + +impl<'a> LinkerCommandLine<'a> { + pub fn get_target_output_for(&self, compiler: CppCompiler) -> Vec { + match compiler { + CppCompiler::CLANG | CppCompiler::GCC => { + vec![Argument::from("-o"), self.target.clone()] + } + CppCompiler::MSVC => vec![self.target.clone()], + } + } + + /// Saves the path at which a compilation product of any translation unit will be placed, + /// in order to add it to the files that will be linked to generate the final product + /// in the two-phase compilation model + pub fn add_byproduct_path(&mut self, path: PathBuf) { + self.byproducts.push(path); + } +} + +/// Holds the generated command line arguments for a concrete compiler +#[derive(Serialize, Deserialize, Default, Debug)] +pub struct Commands<'a> { + pub general_args: Option>, + pub compiler_common_args: Option>, + pub cpp_stdlib: Option>, + pub c_compat_stdlib: Option>, + pub system_modules: Vec>, + pub interfaces: Vec>, + pub implementations: Vec>, + pub sources: Vec>, + pub linker: LinkerCommandLine<'a>, +} + +impl<'a> Commands<'a> { + /// Returns a [std::iter::Chain] (behind the opaque impl clause return type signature) + /// which points to all the generated commands for the two variants of the compilers vendors C++ modular + /// standard libraries implementations (see: [crate::project_model::compiler::StdLibMode]) + /// joined to all the commands generated for every [TranslationUnit] declared by the user for + /// its project + pub fn get_all_command_lines( + &mut self, + ) -> impl Iterator> + Debug { + self.cpp_stdlib + .as_mut_slice() + .iter_mut() + .chain(self.c_compat_stdlib.as_mut_slice().iter_mut()) + .chain(self.system_modules.as_mut_slice().iter_mut()) + .chain(self.interfaces.as_mut_slice().iter_mut()) + .chain(self.implementations.as_mut_slice().iter_mut()) + .chain(self.sources.as_mut_slice().iter_mut()) + } + + pub fn add_linker_file_path(&mut self, path: PathBuf) { + self.linker.add_byproduct_path(path); + } +} diff --git a/zork++/src/lib/domain/commands/mod.rs b/zork++/src/lib/domain/commands/mod.rs new file mode 100644 index 00000000..aefa186b --- /dev/null +++ b/zork++/src/lib/domain/commands/mod.rs @@ -0,0 +1,2 @@ +pub mod arguments; +pub mod command_lines; diff --git a/zork++/src/lib/domain/mod.rs b/zork++/src/lib/domain/mod.rs index ff900e1c..e53f399c 100644 --- a/zork++/src/lib/domain/mod.rs +++ b/zork++/src/lib/domain/mod.rs @@ -1,2 +1,3 @@ +pub mod commands; pub mod target; pub mod translation_unit; diff --git a/zork++/src/lib/domain/target.rs b/zork++/src/lib/domain/target.rs index dc8f28b0..35f9a627 100644 --- a/zork++/src/lib/domain/target.rs +++ b/zork++/src/lib/domain/target.rs @@ -1,6 +1,7 @@ //! The higher abstractions of the program -use crate::{cli::output::arguments::Argument, project_model::sourceset::SourceSet}; +use crate::domain::commands::arguments::Argument; +use crate::project_model::sourceset::SourceSet; /// Bound for the user defined arguments that are passed to the compiler pub trait ExtraArgs<'a> { diff --git a/zork++/src/lib/domain/translation_unit.rs b/zork++/src/lib/domain/translation_unit.rs index e0471b42..3f174c17 100644 --- a/zork++/src/lib/domain/translation_unit.rs +++ b/zork++/src/lib/domain/translation_unit.rs @@ -1,21 +1,14 @@ //! The module which holds the higher and generic abstractions over a source file use crate::project_model::compiler::StdLibMode; +use color_eyre::Report; +use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::fmt::{Debug, Display}; use std::path::PathBuf; +use std::process::ExitStatus; use transient::{Any, Inv}; -/// The different type of translation units that `Zork++` is able to deal with -#[derive(Debug)] -pub enum TranslationUnitKind { - ModuleInterface, - ModuleImplementation, - SourceFile, - ModularStdLib(StdLibMode), - SystemHeader, -} - /// Represents any kind of translation unit and the generic operations /// applicable to all the implementors pub trait TranslationUnit<'a>: AsTranslationUnit<'a> + Any> + Display + Debug { @@ -96,3 +89,66 @@ macro_rules! impl_translation_unit_for { } }; } + +/// The different type of translation units that `Zork++` is able to deal with +#[derive(Debug)] +pub enum TranslationUnitKind { + ModuleInterface, + ModuleImplementation, + SourceFile, + ModularStdLib(StdLibMode), + SystemHeader, +} + +/// The different states of a translation unit in the whole lifecycle of +/// the build process and across different iterations of the same +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq)] +pub enum TranslationUnitStatus { + /// A command that is executed correctly + Success, + /// A skipped command due to previous successful iterations + Cached, + /// A command which is return code indicates an unsuccessful execution + Failed, + /// Whenever a translation unit must be rebuilt + #[default] + PendingToBuild, + /// The associated [`TranslationUnit`] has been deleted from the user's configuration and therefore, + /// it should be removed from the cache as well as its generated byproducts + ToDelete, + /// The execution failed, returning a [`color_eyre::Result`] with the Err variant + Error, +} + +impl From> for TranslationUnitStatus { + fn from(value: color_eyre::Result) -> Self { + helpers::handle_command_execution_result(&value) + } +} + +impl From<&color_eyre::Result> for TranslationUnitStatus { + fn from(value: &color_eyre::Result) -> Self { + helpers::handle_command_execution_result(value) + } +} + +mod helpers { + use crate::domain::translation_unit::TranslationUnitStatus; + use std::process::ExitStatus; + + /// Convenient way of handle a command execution result avoiding duplicate code + pub(crate) fn handle_command_execution_result( + value: &color_eyre::Result, + ) -> TranslationUnitStatus { + match value { + Ok(r) => { + if r.success() { + TranslationUnitStatus::Success + } else { + TranslationUnitStatus::Failed + } + } + Err(_) => TranslationUnitStatus::Error, + } + } +} diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index e430f1be..839c2d83 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -25,7 +25,7 @@ pub mod worker { cache::{self, ZorkCache}, cli::{ input::{CliArgs, Command}, - output::commands, + output::executors, }, compiler::generate_commands, project_model::{compiler::CppCompiler, ZorkModel}, @@ -46,7 +46,7 @@ pub mod worker { .map(Path::new) .unwrap_or(Path::new(".")); let abs_project_root = get_project_root_absolute_path(project_root)?; - + // TODO: falta o do project model, gañans! if let Command::New { ref name, git, @@ -127,10 +127,10 @@ pub mod worker { ); let execution_result = match cli_args.command { - Command::Build => commands::run_generated_commands(program_data, &mut cache), + Command::Build => executors::run_generated_commands(program_data, &mut cache), Command::Run | Command::Test => { - match commands::run_generated_commands(program_data, &mut cache) { - Ok(_) => commands::autorun_generated_binary( + match executors::run_generated_commands(program_data, &mut cache) { + Ok(_) => executors::autorun_generated_binary( &program_data.compiler.cpp_compiler, &program_data.build.output_dir, &program_data.executable.executable_name, diff --git a/zork++/src/lib/project_model/compiler.rs b/zork++/src/lib/project_model/compiler.rs index eaac2a05..e28f616a 100644 --- a/zork++/src/lib/project_model/compiler.rs +++ b/zork++/src/lib/project_model/compiler.rs @@ -1,7 +1,7 @@ use core::fmt; use std::borrow::Cow; -use crate::cli::output::arguments::Argument; +use crate::domain::commands::arguments::Argument; use serde::{Deserialize, Serialize}; use crate::domain::target::ExtraArgs; diff --git a/zork++/src/lib/project_model/executable.rs b/zork++/src/lib/project_model/executable.rs index 2d3f1793..a7d4fed2 100644 --- a/zork++/src/lib/project_model/executable.rs +++ b/zork++/src/lib/project_model/executable.rs @@ -3,10 +3,8 @@ use std::borrow::Cow; use serde::{Deserialize, Serialize}; use super::sourceset::SourceSet; -use crate::{ - cli::output::arguments::Argument, - domain::target::{ExecutableTarget, ExtraArgs}, -}; +use crate::domain::commands::arguments::Argument; +use crate::domain::target::{ExecutableTarget, ExtraArgs}; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct ExecutableModel<'a> { diff --git a/zork++/src/lib/project_model/sourceset.rs b/zork++/src/lib/project_model/sourceset.rs index 20cd5f27..abfbcbdb 100644 --- a/zork++/src/lib/project_model/sourceset.rs +++ b/zork++/src/lib/project_model/sourceset.rs @@ -2,11 +2,11 @@ use core::fmt; use std::borrow::Cow; use std::path::PathBuf; +use crate::domain::commands::arguments::Argument; use color_eyre::{eyre::Context, Result}; use serde::{Deserialize, Serialize}; use transient::Transient; -use crate::cli::output::arguments::Argument; use crate::domain::translation_unit::TranslationUnit; use crate::impl_translation_unit_for; diff --git a/zork++/src/lib/project_model/tests.rs b/zork++/src/lib/project_model/tests.rs index 48be167a..7b498f1a 100644 --- a/zork++/src/lib/project_model/tests.rs +++ b/zork++/src/lib/project_model/tests.rs @@ -1,9 +1,7 @@ use serde::{Deserialize, Serialize}; -use crate::{ - cli::output::arguments::Argument, - domain::target::{ExecutableTarget, ExtraArgs}, -}; +use crate::domain::commands::arguments::Argument; +use crate::domain::target::{ExecutableTarget, ExtraArgs}; use std::borrow::Cow; use super::sourceset::SourceSet; diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 57f63dff..be20b8e3 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -1,9 +1,9 @@ use crate::cli::input::CliArgs; +use crate::domain::commands::arguments::Argument; use crate::project_model::modules::SystemModule; use crate::project_model::sourceset::SourceFile; use crate::{ - cli::output::arguments::Argument, config_file::{ build::BuildAttribute, compiler::CompilerAttribute, From 876fe2be6cf0f09543ef364e7c262443680f5771 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sat, 20 Jul 2024 12:07:24 +0200 Subject: [PATCH 48/73] feat(targets)!: WIP. Towards the named targets processing model --- zork++/src/lib/compiler/mod.rs | 84 ++++++++++++------- zork++/src/lib/domain/commands/arguments.rs | 2 +- .../src/lib/domain/commands/command_lines.rs | 38 +++++++-- zork++/src/lib/domain/target.rs | 27 ++++++ zork++/src/lib/domain/translation_unit.rs | 7 +- zork++/src/lib/project_model/mod.rs | 1 + zork++/src/lib/project_model/target.rs | 12 +++ zork++/src/lib/utils/constants.rs | 2 + 8 files changed, 132 insertions(+), 41 deletions(-) create mode 100644 zork++/src/lib/project_model/target.rs diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index ee1f9f6e..50c94f0f 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -52,8 +52,10 @@ pub fn generate_commands<'a>( // Translation units and linker - // 1st - Build the modules + // Generates commands for the modules process_modules(model, cache, cli_args)?; + // Generate commands for the declared targets + process_targets(model, cache, cli_args)?; // 2nd - Generate the commands for the non-module sources generate_sources_cmds_args(model, cache, cli_args)?; // 3rd - Generate the linker command for the 'target' declared by the user @@ -138,6 +140,14 @@ fn process_modules<'a>( Ok(()) } +fn process_targets<'a>( + model: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + cli_args: &'a CliArgs, +) -> Result<()> { + Ok(()) +} + /// Processor for generate the commands of the non-modular translation units /// /// *NOTE*: This will be changed on the future, when we decide how we should architecture the implementation @@ -287,7 +297,7 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( .with_context(|| helpers::wrong_downcast_msg(translation_unit))?; modules::generate_module_implementation_cmd(model, cache, resolved_tu) } - TranslationUnitKind::SourceFile => { + TranslationUnitKind::SourceFile(related_target) => { let target = if cli_args.command.eq(&Command::Test) { &model.tests as &dyn ExecutableTarget } else { @@ -296,7 +306,13 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( let resolved_tu = transient::Downcast::downcast_ref::(tu_with_erased_type) .with_context(|| helpers::wrong_downcast_msg(translation_unit))?; - sources::generate_sources_arguments(model, cache, resolved_tu, target) + sources::generate_sources_arguments( + model, + cache, + resolved_tu, + &related_target, + target, + )?; } TranslationUnitKind::SystemHeader => { let resolved_tu = @@ -313,7 +329,7 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( /// Command line arguments generators procedures for C++ standard modules mod modules { - use std::path::Path; + use std::path::{Path, PathBuf}; use crate::cache::ZorkCache; use crate::compiler::helpers; @@ -391,11 +407,7 @@ mod modules { // The input file arguments.push(interface.path()); - cache - .generated_commands - .add_linker_file_path(binary_module_ifc); - - let cmd_line = SourceCommandLine::new(interface, arguments); + let cmd_line = SourceCommandLine::new(interface, arguments, binary_module_ifc); cache.generated_commands.interfaces.push(cmd_line); } @@ -438,9 +450,7 @@ mod modules { } } - cache.generated_commands.add_linker_file_path(obj_file_path); - - let cmd = SourceCommandLine::new(implementation.to_owned(), arguments); + let cmd = SourceCommandLine::new(implementation.to_owned(), arguments, obj_file_path); cache.generated_commands.implementations.push(cmd); } @@ -484,6 +494,7 @@ mod modules { filename: sys_module.to_string(), args, status: TranslationUnitStatus::PendingToBuild, + byproduct: /* TODO:!!!!!!: */ PathBuf::default() }; cache.generated_commands.system_modules.push(cmd); } @@ -518,10 +529,12 @@ mod sources { use crate::cache::ZorkCache; use crate::domain::commands::arguments::Arguments; use crate::domain::commands::command_lines::SourceCommandLine; - use crate::domain::target::ExecutableTarget; + use crate::domain::target::{ExecutableTarget, TargetIdentifier}; use crate::domain::translation_unit::TranslationUnit; use crate::project_model::sourceset::SourceFile; use crate::project_model::{compiler::CppCompiler, ZorkModel}; + use crate::utils::constants::error_messages; + use color_eyre::eyre::{ContextCompat, Result}; use super::helpers; @@ -530,8 +543,9 @@ mod sources { model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, source: &'a SourceFile<'a>, + target_identifier: &TargetIdentifier, target: &'a (impl ExecutableTarget<'a> + ?Sized), - ) { + ) -> Result<()> { let compiler = model.compiler.cpp_compiler; let out_dir = model.build.output_dir.as_ref(); @@ -547,9 +561,22 @@ mod sources { arguments.push(format!("{fo}{}", obj_file.display())); arguments.push(source.path()); - let command_line = SourceCommandLine::new(source, arguments); - cache.generated_commands.sources.push(command_line); - cache.generated_commands.add_linker_file_path(obj_file) + let command_line = SourceCommandLine::new(source, arguments, obj_file); + cache + .generated_commands + .targets + .get_mut(target_identifier) + .with_context(|| { + format!( + "{}: {:?}", + error_messages::TARGET_ENTRY_NOT_FOUND, + target_identifier + ) + })? + .sources + .push(command_line); + + Ok(()) } } @@ -607,18 +634,17 @@ mod helpers { compiler: CppCompiler, module_name: &str, ) -> PathBuf { - out_dir - .join(compiler.as_ref()) - .join(dir_names::MODULES) - .join(dir_names::INTERFACES) - .join(format!( - "{module_name}.{}", - if compiler.eq(&CppCompiler::MSVC) { - compiler.get_obj_file_extension() - } else { - compiler.get_typical_bmi_extension() - } - )) + let base = out_dir.join(compiler.as_ref()); + let (intermediate, extension) = if compiler.eq(&CppCompiler::MSVC) { + let intermediate = base.join(dir_names::OBJECT_FILES); + (intermediate, compiler.get_obj_file_extension()) + } else { + let intermediate = base.join(dir_names::MODULES).join(dir_names::INTERFACES); + (intermediate, compiler.get_typical_bmi_extension()) + }; + + base.join(intermediate) + .join(format!("{module_name}.{}", extension)) } /// Generates the [`PathBuf`] of the resultant `.obj` file of a [`TranslationUnit`] where the diff --git a/zork++/src/lib/domain/commands/arguments.rs b/zork++/src/lib/domain/commands/arguments.rs index bfe39065..5af9fb5a 100644 --- a/zork++/src/lib/domain/commands/arguments.rs +++ b/zork++/src/lib/domain/commands/arguments.rs @@ -102,7 +102,7 @@ impl<'a> core::fmt::Display for Argument<'a> { } /// Strong type for represent a linear collection of [`Argument`] -#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct Arguments<'a>(Vec>); impl<'a> core::fmt::Display for Arguments<'a> { diff --git a/zork++/src/lib/domain/commands/command_lines.rs b/zork++/src/lib/domain/commands/command_lines.rs index 158736c8..7179303f 100644 --- a/zork++/src/lib/domain/commands/command_lines.rs +++ b/zork++/src/lib/domain/commands/command_lines.rs @@ -1,8 +1,12 @@ use crate::compiler::data_factory::{CommonArgs, CompilerCommonArguments}; use crate::domain::commands::arguments::{Argument, Arguments}; +use crate::domain::target::{Target, TargetIdentifier}; use crate::domain::translation_unit::{TranslationUnit, TranslationUnitStatus}; use crate::project_model::compiler::CppCompiler; +use crate::utils::constants::error_messages; +use color_eyre::eyre::{ContextCompat, Result}; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::fmt::Debug; use std::path::{Path, PathBuf}; @@ -15,21 +19,23 @@ use std::path::{Path, PathBuf}; /// * status*: A [`TranslationUnitStatus`] that represents all the different phases that a source command /// line can have among all the different iterations of the program, changing according to the modifications /// over the translation unit in the fs and the result of the build execution -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct SourceCommandLine<'a> { pub directory: PathBuf, pub filename: String, pub args: Arguments<'a>, pub status: TranslationUnitStatus, + pub byproduct: PathBuf, } impl<'a> SourceCommandLine<'a> { - pub fn new>(tu: &T, args: Arguments<'a>) -> Self { + pub fn new>(tu: &T, args: Arguments<'a>, byproduct: PathBuf) -> Self { Self { directory: PathBuf::from(tu.parent()), filename: tu.filename(), args, status: TranslationUnitStatus::PendingToBuild, + byproduct, } } @@ -42,8 +48,10 @@ impl<'a> SourceCommandLine<'a> { } } -#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)] + pub struct LinkerCommandLine<'a> { + link_modules: bool, pub target: Argument<'a>, pub byproducts: Arguments<'a>, pub extra_args: Arguments<'a>, @@ -78,8 +86,7 @@ pub struct Commands<'a> { pub system_modules: Vec>, pub interfaces: Vec>, pub implementations: Vec>, - pub sources: Vec>, - pub linker: LinkerCommandLine<'a>, + pub targets: HashMap, Target<'a>>, } impl<'a> Commands<'a> { @@ -98,10 +105,25 @@ impl<'a> Commands<'a> { .chain(self.system_modules.as_mut_slice().iter_mut()) .chain(self.interfaces.as_mut_slice().iter_mut()) .chain(self.implementations.as_mut_slice().iter_mut()) - .chain(self.sources.as_mut_slice().iter_mut()) + // TODO: .chain(self.sources.as_mut_slice().iter_mut()) } - pub fn add_linker_file_path(&mut self, path: PathBuf) { - self.linker.add_byproduct_path(path); + pub fn add_linker_file_path( + &mut self, + target_identifier: &TargetIdentifier, + path: PathBuf, + ) -> Result<()> { + self.targets + .get_mut(target_identifier) + .with_context(|| { + format!( + "{}: {:?}", + error_messages::TARGET_ENTRY_NOT_FOUND, + target_identifier + ) + })? + .linker + .add_byproduct_path(path); + Ok(()) } } diff --git a/zork++/src/lib/domain/target.rs b/zork++/src/lib/domain/target.rs index 35f9a627..f8f392c7 100644 --- a/zork++/src/lib/domain/target.rs +++ b/zork++/src/lib/domain/target.rs @@ -1,7 +1,34 @@ //! The higher abstractions of the program use crate::domain::commands::arguments::Argument; +use crate::domain::commands::command_lines::{LinkerCommandLine, SourceCommandLine}; use crate::project_model::sourceset::SourceSet; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; + +/// The final product that will be made after the building process +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct Target<'a> { + // pub identifier: Cow<'a, str>, + pub sources: Vec>, + pub linker: LinkerCommandLine<'a>, + pub kind: TargetKind, +} + +/// Strong type for storing the target unique identifier, which instead of being +/// composite within the [`Target`] struct, is externalized in this wrapped type, so +/// we can use a strong type on the [`Commands.targets`] container +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] +pub struct TargetIdentifier<'a>(Cow<'a, str>); + +/// The different types of final products +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +pub enum TargetKind { + #[default] + Executable, + StaticLib, + DyLib, +} /// Bound for the user defined arguments that are passed to the compiler pub trait ExtraArgs<'a> { diff --git a/zork++/src/lib/domain/translation_unit.rs b/zork++/src/lib/domain/translation_unit.rs index 3f174c17..949b3e70 100644 --- a/zork++/src/lib/domain/translation_unit.rs +++ b/zork++/src/lib/domain/translation_unit.rs @@ -1,5 +1,6 @@ //! The module which holds the higher and generic abstractions over a source file +use crate::domain::target::TargetIdentifier; use crate::project_model::compiler::StdLibMode; use color_eyre::Report; use serde::{Deserialize, Serialize}; @@ -92,17 +93,17 @@ macro_rules! impl_translation_unit_for { /// The different type of translation units that `Zork++` is able to deal with #[derive(Debug)] -pub enum TranslationUnitKind { +pub enum TranslationUnitKind<'a> { ModuleInterface, ModuleImplementation, - SourceFile, + SourceFile(TargetIdentifier<'a>), ModularStdLib(StdLibMode), SystemHeader, } /// The different states of a translation unit in the whole lifecycle of /// the build process and across different iterations of the same -#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, Eq, PartialEq)] pub enum TranslationUnitStatus { /// A command that is executed correctly Success, diff --git a/zork++/src/lib/project_model/mod.rs b/zork++/src/lib/project_model/mod.rs index 6a0e968a..1c53c6eb 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -4,6 +4,7 @@ pub mod executable; pub mod modules; pub mod project; pub mod sourceset; +mod target; pub mod tests; use std::fmt::Debug; diff --git a/zork++/src/lib/project_model/target.rs b/zork++/src/lib/project_model/target.rs new file mode 100644 index 00000000..6d6faaf4 --- /dev/null +++ b/zork++/src/lib/project_model/target.rs @@ -0,0 +1,12 @@ +use crate::domain::target::TargetKind; +use crate::project_model::sourceset::SourceSet; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct TargetModel<'a> { + pub output_name: Cow<'a, str>, + pub sources: SourceSet<'a>, + pub extra_args: Vec>, + pub kind: TargetKind, +} diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index 288ea6f0..01069876 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -39,6 +39,8 @@ pub mod error_messages { pub const GENERAL_ARGS_NOT_FOUND: &str = "Something went wrong loading the general arguments"; pub const PROJECT_MODEL_MAPPING: &str = "Error building the project model"; pub const PROJECT_MODEL_SAVE: &str = "Error caching and saving to the fs the project model"; + pub const TARGET_ENTRY_NOT_FOUND: &str = + "Unlikely error happened while adding linkage data to a target"; pub const COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND: &str = "Something went wrong loading the general arguments"; pub const DEFAULT_OF_COMPILER_COMMON_ARGUMENTS: &str = From 11c54baadf2be440de181f4d7e5d7980533e976b Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 21 Jul 2024 11:40:15 +0200 Subject: [PATCH 49/73] feat(WIP)!: Setting the basis for handle any number of user declared targets --- zork++/Cargo.lock | 146 +++++++++--------- zork++/Cargo.toml | 1 - zork++/src/lib/cache/mod.rs | 21 ++- zork++/src/lib/cli/output/executors.rs | 9 +- zork++/src/lib/compiler/mod.rs | 21 +-- zork++/src/lib/config_file/mod.rs | 59 +++++-- zork++/src/lib/config_file/target.rs | 48 ++++++ zork++/src/lib/domain/commands/arguments.rs | 2 +- .../src/lib/domain/commands/command_lines.rs | 2 +- zork++/src/lib/domain/target.rs | 2 +- zork++/src/lib/project_model/mod.rs | 12 +- zork++/src/lib/utils/reader.rs | 112 ++++++++------ 12 files changed, 272 insertions(+), 163 deletions(-) create mode 100644 zork++/src/lib/config_file/target.rs diff --git a/zork++/Cargo.lock b/zork++/Cargo.lock index 25b7782a..60c3490d 100644 --- a/zork++/Cargo.lock +++ b/zork++/Cargo.lock @@ -79,9 +79,9 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys", ] @@ -136,9 +136,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bumpalo" @@ -154,9 +154,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.98" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" [[package]] name = "cfg-if" @@ -220,9 +220,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" dependencies = [ "clap_builder", "clap_derive", @@ -230,21 +230,21 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.0", + "clap_lex 0.7.1", "strsim", ] [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck", "proc-macro2", @@ -263,9 +263,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "color-eyre" @@ -361,9 +361,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "env_filter" @@ -550,9 +550,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" @@ -568,21 +568,21 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] @@ -613,9 +613,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "os_str_bytes" @@ -659,9 +659,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -697,9 +697,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -709,9 +709,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -720,9 +720,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rustc-demangle" @@ -736,7 +736,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -760,18 +760,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.202" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", @@ -780,9 +780,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -797,9 +797,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.66" +version = "2.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" dependencies = [ "proc-macro2", "quote", @@ -826,18 +826,18 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -863,14 +863,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_merge" -version = "0.1.0" -dependencies = [ - "serde", - "toml", -] - [[package]] name = "transient" version = "0.4.0" @@ -930,9 +922,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "walkdir" @@ -1059,9 +1051,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1075,58 +1067,58 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "zork" version = "0.9.0" dependencies = [ "chrono", - "clap 4.5.4", + "clap 4.5.9", "color-eyre", "criterion", "env_logger", diff --git a/zork++/Cargo.toml b/zork++/Cargo.toml index 156450f6..76a3e075 100644 --- a/zork++/Cargo.toml +++ b/zork++/Cargo.toml @@ -1,4 +1,3 @@ -workspace = { members = ["merging_toml"] } [package] name = "zork" version = "0.9.0" diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 4a7b1c8c..2d0e77ec 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -17,6 +17,7 @@ use std::{ use crate::config_file::ZorkConfigFile; use crate::domain::commands::command_lines::{Commands, SourceCommandLine}; +use crate::domain::target::TargetIdentifier; use crate::domain::translation_unit::{TranslationUnit, TranslationUnitKind}; use crate::project_model::sourceset::SourceFile; use crate::utils::constants::{dir_names, error_messages}; @@ -106,12 +107,12 @@ impl<'a> ZorkCache<'a> { pub fn get_cmd_for_translation_unit_kind>( &mut self, translation_unit: &T, - translation_unit_kind: &TranslationUnitKind, + translation_unit_kind: &TranslationUnitKind<'a>, ) -> Option<&mut SourceCommandLine<'a>> { match translation_unit_kind { TranslationUnitKind::ModuleInterface => self.get_module_ifc_cmd(translation_unit), TranslationUnitKind::ModuleImplementation => self.get_module_impl_cmd(translation_unit), - TranslationUnitKind::SourceFile => self.get_source_cmd(translation_unit), + TranslationUnitKind::SourceFile(for_target) => self.get_source_cmd(translation_unit, for_target), TranslationUnitKind::SystemHeader => self.get_system_module_cmd(translation_unit), TranslationUnitKind::ModularStdLib(stdlib_mode) => match stdlib_mode { StdLibMode::Cpp => self.get_cpp_stdlib_cmd(), @@ -143,8 +144,12 @@ impl<'a> ZorkCache<'a> { fn get_source_cmd>( &mut self, source: &T, + for_target: &TargetIdentifier<'a> ) -> Option<&mut SourceCommandLine<'a>> { self.generated_commands + .targets + .get_mut(for_target) + .expect(error_messages::FAILURE_TARGET_SOURCES) .sources .iter_mut() .find(|cached_tu| source.path().eq(&cached_tu.path())) @@ -250,7 +255,7 @@ impl<'a> ZorkCache<'a> { .chain(generated_commands.c_compat_stdlib.as_slice().iter()) .chain(generated_commands.interfaces.iter()) .chain(generated_commands.implementations.iter()) - .chain(generated_commands.sources.iter()) + // TODO: .chain(generated_commands.sources.iter()) } /// The current integer value that is the total of commands generated for all the @@ -261,7 +266,7 @@ impl<'a> ZorkCache<'a> { latest_commands.interfaces.len() + latest_commands.implementations.len() - + latest_commands.sources.len() + // + latest_commands.sources.len() // TODO: + latest_commands.system_modules.len() + 2 // the cpp_stdlib and the c_compat_stdlib } @@ -489,14 +494,16 @@ mod helpers { ) || remove_if_needed_from_cache_and_count_changes( &mut cache.generated_commands.implementations, &program_data.modules.implementations, - ) || remove_if_needed_from_cache_and_count_changes( + ) + /* || + remove_if_needed_from_cache_and_count_changes( &mut cache.generated_commands.sources, if !cli_args.command.eq(&Command::Test) { &program_data.executable.sourceset.sources } else { &program_data.tests.sourceset.sources - }, - ) || remove_if_needed_from_cache_and_count_changes( + }, ) */ + || remove_if_needed_from_cache_and_count_changes( &mut cache.generated_commands.system_modules, &program_data.modules.sys_modules, ) diff --git a/zork++/src/lib/cli/output/executors.rs b/zork++/src/lib/cli/output/executors.rs index d26b0d88..86b13db1 100644 --- a/zork++/src/lib/cli/output/executors.rs +++ b/zork++/src/lib/cli/output/executors.rs @@ -46,6 +46,8 @@ pub fn run_generated_commands<'a>( CppCompiler::GCC => &cache.compilers_metadata.gcc.env_vars, }; + let mut mock = vec![]; + let translation_units_commands: Vec<&mut SourceCommandLine> = helpers::get_translation_units_commands( // Independent borrows to avoid have borrow checker yielding at me @@ -54,7 +56,8 @@ pub fn run_generated_commands<'a>( &mut generated_commands.system_modules, &mut generated_commands.interfaces, &mut generated_commands.implementations, - &mut generated_commands.sources, + // &mut generated_commands.sources, + &mut mock, ); let compile_but_dont_link: [Argument; 1] = @@ -86,7 +89,7 @@ pub fn run_generated_commands<'a>( } } - log::info!("Processing the linker command line..."); + /* log::info!("Processing the linker command line..."); let r = helpers::execute_linker_command_line( program_data, general_args, @@ -102,7 +105,7 @@ pub fn run_generated_commands<'a>( return Err(eyre!( "Ending the program, because the linker command line execution failed", )); - } + } */ Ok(()) } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 50c94f0f..410e58cc 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -9,6 +9,7 @@ use std::path::Path; use color_eyre::Result; use crate::domain::commands::arguments::Argument; +use crate::domain::target::TargetIdentifier; use crate::domain::translation_unit::TranslationUnitStatus; use crate::project_model::modules::SystemModule; use crate::utils::constants::error_messages; @@ -56,10 +57,6 @@ pub fn generate_commands<'a>( process_modules(model, cache, cli_args)?; // Generate commands for the declared targets process_targets(model, cache, cli_args)?; - // 2nd - Generate the commands for the non-module sources - generate_sources_cmds_args(model, cache, cli_args)?; - // 3rd - Generate the linker command for the 'target' declared by the user - generate_linkage_targets_commands(model, cache, cli_args); Ok(()) } @@ -145,6 +142,12 @@ fn process_targets<'a>( cache: &mut ZorkCache<'a>, cli_args: &'a CliArgs, ) -> Result<()> { + for target in &model.targets { + // 2nd - Generate the commands for the non-module sources + generate_sources_cmds_args(model, cache, cli_args)?; + // 3rd - Generate the linker command for the 'target' declared by the user + generate_linkage_targets_commands(model, cache, cli_args); + } Ok(()) } @@ -175,7 +178,7 @@ fn generate_sources_cmds_args<'a>( cache, cli_args, srcs, - TranslationUnitKind::SourceFile, + TranslationUnitKind::SourceFile(TargetIdentifier::default()), // TODO: ) .with_context(|| error_messages::FAILURE_TARGET_SOURCES) } @@ -213,7 +216,7 @@ pub fn generate_linker_general_command_line_args<'a>( ) { log::info!("Generating the linker command line..."); - let linker = &mut cache.generated_commands.linker; + let linker = &mut cache.generated_commands.targets.get_mut(&TargetIdentifier::default()).unwrap().linker; // TODO: let compiler = &model.compiler.cpp_compiler; let out_dir: &Path = model.build.output_dir.as_ref(); @@ -250,7 +253,7 @@ fn process_kind_translation_units<'a, T: TranslationUnit<'a>>( cache: &mut ZorkCache<'a>, cli_args: &'a CliArgs, translation_units: &'a [T], - for_kind: TranslationUnitKind, + for_kind: TranslationUnitKind<'a>, ) -> Result<()> { for translation_unit in translation_units.iter() { process_kind_translation_unit(model, cache, cli_args, translation_unit, &for_kind)? @@ -264,7 +267,7 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( cache: &mut ZorkCache<'a>, cli_args: &'a CliArgs, translation_unit: &'a T, - for_kind: &TranslationUnitKind, + for_kind: &TranslationUnitKind<'a>, ) -> Result<()> { let compiler = model.compiler.cpp_compiler; let lpe = cache.metadata.last_program_execution; @@ -543,7 +546,7 @@ mod sources { model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, source: &'a SourceFile<'a>, - target_identifier: &TargetIdentifier, + target_identifier: &TargetIdentifier<'a>, target: &'a (impl ExecutableTarget<'a> + ?Sized), ) -> Result<()> { let compiler = model.compiler.cpp_compiler; diff --git a/zork++/src/lib/config_file/mod.rs b/zork++/src/lib/config_file/mod.rs index dbc9c994..256edeb7 100644 --- a/zork++/src/lib/config_file/mod.rs +++ b/zork++/src/lib/config_file/mod.rs @@ -1,26 +1,30 @@ //! root file for the crate where the datastructures that holds the TOML //! parsed data lives. -pub mod build; +pub mod project; pub mod compiler; -pub mod executable; +pub mod build; pub mod modules; -pub mod project; +pub mod target; +pub mod executable; pub mod tests; -use std::fmt::Debug; +use std::{collections::HashMap, fmt::Debug}; use serde::{Deserialize, Serialize}; use self::{ build::BuildAttribute, compiler::CompilerAttribute, executable::ExecutableAttribute, - modules::ModulesAttribute, project::ProjectAttribute, tests::TestsAttribute, + modules::ModulesAttribute, target::TargetAttribute, project::ProjectAttribute, tests::TestsAttribute }; /// ```rust /// use zork::config_file::{ /// ZorkConfigFile, -/// compiler::CppCompiler +/// compiler::{CppCompiler, LanguageLevel}, +/// target::TargetAttribute /// }; +/// use zork::domain::target::TargetKind; +/// use std::collections::HashMap; /// /// const CONFIG_FILE_MOCK: &str = r#" /// [project] @@ -30,14 +34,49 @@ use self::{ /// [compiler] /// cpp_compiler = 'clang' /// cpp_standard = '20' +/// +/// [targets.executable] +/// output_name = 'final binary' +/// sources = [ 'main.cpp' ] +/// extra_args = [ '-Wall' ] +/// +/// [targets.tests] +/// sources = [ 'tests_main.cpp' ] +/// target_kind = 'executable' + +/// [targets.other_tests] +/// sources = [ 'other_tests_main.cpp' ] +/// target_kind = 'executable' /// "#; /// /// let config: ZorkConfigFile = toml::from_str(CONFIG_FILE_MOCK) /// .expect("A failure happened parsing the Zork toml file"); /// /// let compiler_attribute = &config.compiler; -/// /// assert_eq!(compiler_attribute.cpp_compiler, CppCompiler::CLANG); +/// assert_eq!(compiler_attribute.cpp_standard, LanguageLevel::CPP20); +/// +/// let targets: &HashMap<&str, TargetAttribute<'_>> = &config.targets; +/// assert!(!targets.is_empty()); +/// +/// let executable_target: &TargetAttribute<'_> = targets.get("executable").expect("Target named +/// 'executable' not found on the configuration"); +/// assert!(executable_target.output_name.unwrap().contains("final binary")); +/// assert!(executable_target.sources.contains(&"main.cpp")); +/// assert!(executable_target.extra_args.as_ref().unwrap().contains(&"-Wall")); +/// assert!(executable_target.kind.unwrap_or_default().eq(&TargetKind::Executable)); +/// +/// let tests_target: &TargetAttribute<'_> = targets.get("tests").expect("Target named +/// 'tests' not found on the configuration"); +/// assert!(tests_target.sources.contains(&"tests_main.cpp")); +/// assert!(tests_target.extra_args.is_none()); +/// assert!(tests_target.kind.unwrap_or_default().eq(&TargetKind::Executable)); +/// +/// let other_tests_target: &TargetAttribute<'_> = targets.get("other_tests").expect("Target named +/// 'other_tests' not found on the configuration"); +/// assert!(other_tests_target.sources.contains(&"other_tests_main.cpp")); +/// assert!(other_tests_target.extra_args.is_none()); +/// assert!(other_tests_target.kind.unwrap_or_default().eq(&TargetKind::Executable)); /// ``` /// The [`ZorkConfigFile`] is the type that holds /// the whole hierarchy of Zork++ config file attributes @@ -51,10 +90,12 @@ pub struct ZorkConfigFile<'a> { #[serde(borrow)] pub build: Option>, #[serde(borrow)] - pub executable: Option>, - #[serde(borrow)] pub modules: Option>, #[serde(borrow)] + pub targets: HashMap<&'a str, TargetAttribute<'a>>, + #[serde(borrow)] + pub executable: Option>, + #[serde(borrow)] pub tests: Option>, } diff --git a/zork++/src/lib/config_file/target.rs b/zork++/src/lib/config_file/target.rs new file mode 100644 index 00000000..9ed1485c --- /dev/null +++ b/zork++/src/lib/config_file/target.rs @@ -0,0 +1,48 @@ +//! Type for holds the Targets build details + +use serde::{Deserialize, Serialize}; + +use crate::domain::target::TargetKind; + +/// [`TargetAttribute`] - The type for holding the build details of every +/// user defined target +/// * `output_name`- The name with which the final byproduct will be generated +/// * `sources` - The sources to be included in the compilation of this target +/// * `extra_args` - Holds extra arguments that the user wants to introduce +/// * `kind` - Determined which type of byproduct will be generated (binary, library...) +/// +/// ### Tests +/// +/// ```rust +/// use zork::config_file::target::TargetAttribute; +/// const CONFIG_FILE_MOCK: &str = r#" +/// #[target.executable] +/// output_name = "some_executable" +/// sources = [ '*.cpp' ] +/// extra_args = ['-Wall'] +/// kind = "Executable" +/// "#; +/// +/// let config: TargetAttribute = toml::from_str(CONFIG_FILE_MOCK) +/// .expect("A failure happened parsing the Zork toml file"); +/// +/// assert_eq!(config.executable_name, Some("some_executable")); +/// assert_eq!(config.sources, Some(vec!["*.cpp"])); +/// assert_eq!(config.extra_args, Some(vec!["-Wall"])) +/// assert_eq!(config.kind, TargetKind::Executable) +/// ``` +/// > Note: TOML table are toml commented (#) to allow us to parse +/// the inner attributes as the direct type that they belongs to. +/// That commented tables aren't the real TOML, they are just there +/// for testing and exemplification purposes of the inner attributes +/// of the configuration file. +/// +/// For a test over a real example, please look at the +/// [`zork::config_file::ZorkConfigFile`] doc-test +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct TargetAttribute<'a> { + pub output_name: Option<&'a str>, + pub sources: Vec<&'a str>, + pub extra_args: Option>, + pub kind: Option, +} diff --git a/zork++/src/lib/domain/commands/arguments.rs b/zork++/src/lib/domain/commands/arguments.rs index 5af9fb5a..17eddef6 100644 --- a/zork++/src/lib/domain/commands/arguments.rs +++ b/zork++/src/lib/domain/commands/arguments.rs @@ -304,6 +304,6 @@ pub mod msvc_args { "/Fo{}", stdlib_obj_path.display() }); - SourceCommandLine::new(stdlib_sf, arguments) + SourceCommandLine::new(stdlib_sf, arguments, stdlib_obj_path.to_path_buf()) } } diff --git a/zork++/src/lib/domain/commands/command_lines.rs b/zork++/src/lib/domain/commands/command_lines.rs index 7179303f..d9acaf43 100644 --- a/zork++/src/lib/domain/commands/command_lines.rs +++ b/zork++/src/lib/domain/commands/command_lines.rs @@ -110,7 +110,7 @@ impl<'a> Commands<'a> { pub fn add_linker_file_path( &mut self, - target_identifier: &TargetIdentifier, + target_identifier: &TargetIdentifier<'a>, path: PathBuf, ) -> Result<()> { self.targets diff --git a/zork++/src/lib/domain/target.rs b/zork++/src/lib/domain/target.rs index f8f392c7..a7ef00f2 100644 --- a/zork++/src/lib/domain/target.rs +++ b/zork++/src/lib/domain/target.rs @@ -22,7 +22,7 @@ pub struct Target<'a> { pub struct TargetIdentifier<'a>(Cow<'a, str>); /// The different types of final products -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Copy, Clone)] pub enum TargetKind { #[default] Executable, diff --git a/zork++/src/lib/project_model/mod.rs b/zork++/src/lib/project_model/mod.rs index 1c53c6eb..70081c91 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -4,22 +4,21 @@ pub mod executable; pub mod modules; pub mod project; pub mod sourceset; -mod target; +pub mod target; pub mod tests; -use std::fmt::Debug; +use std::{collections::HashMap, fmt::Debug}; use color_eyre::eyre::Context; use color_eyre::Result; use serde::{Deserialize, Serialize}; -use crate::cache::ZorkCache; +use crate::{cache::ZorkCache, domain::target::TargetIdentifier}; use crate::utils; use self::{ - build::BuildModel, compiler::CompilerModel, executable::ExecutableModel, modules::ModulesModel, - project::ProjectModel, tests::TestsModel, + build::BuildModel, compiler::CompilerModel, executable::ExecutableModel, modules::ModulesModel, project::ProjectModel, target::TargetModel, tests::TestsModel }; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] @@ -27,8 +26,9 @@ pub struct ZorkModel<'a> { pub project: ProjectModel<'a>, pub compiler: CompilerModel<'a>, pub build: BuildModel, - pub executable: ExecutableModel<'a>, pub modules: ModulesModel<'a>, + pub targets: HashMap, TargetModel<'a>>, + pub executable: ExecutableModel<'a>, pub tests: TestsModel<'a>, } diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index be20b8e3..733b3851 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -1,8 +1,11 @@ use crate::cli::input::CliArgs; +use crate::config_file::target::TargetAttribute; use crate::domain::commands::arguments::Argument; +use crate::domain::target::TargetIdentifier; use crate::project_model::modules::SystemModule; use crate::project_model::sourceset::SourceFile; +use crate::project_model::target::TargetModel; use crate::{ config_file::{ build::BuildAttribute, @@ -30,6 +33,7 @@ use crate::{ use chrono::{DateTime, Utc}; use color_eyre::{eyre::eyre, Result}; use std::borrow::Cow; +use std::collections::HashMap; use std::path::{Path, PathBuf}; use walkdir::WalkDir; @@ -103,12 +107,15 @@ pub fn build_model<'a>( let compiler = assemble_compiler_model(config.compiler, cli_args); let build = assemble_build_model(config.build, absolute_project_root); + let modules = assemble_modules_model(config.modules, absolute_project_root); + + let targets = assemble_targets_model(config.targets, absolute_project_root); + let executable = assemble_executable_model( Cow::Borrowed(proj_name), config.executable, absolute_project_root, ); - let modules = assemble_modules_model(config.modules, absolute_project_root); let tests = assemble_tests_model( Cow::Borrowed(proj_name), config.tests, @@ -119,12 +126,14 @@ pub fn build_model<'a>( project, compiler, build, - executable, modules, + targets, + executable, tests, }) } + fn assemble_project_model(config: ProjectAttribute) -> ProjectModel { ProjectModel { name: Cow::Borrowed(config.name), @@ -176,42 +185,6 @@ fn assemble_build_model(config: Option, project_root: &Path) -> } } -//noinspection ALL -fn assemble_executable_model<'a>( - project_name: Cow<'a, str>, - config: Option>, - project_root: &Path, -) -> ExecutableModel<'a> { - let config = config.as_ref(); - - let executable_name = config - .and_then(|exe| exe.executable_name) - .map(Cow::Borrowed) - .unwrap_or(project_name); - - let sources = config - .and_then(|exe| exe.sources.as_ref()) - .map(|srcs| { - srcs.iter() - .map(|src| Cow::Borrowed(*src)) - .collect::>>() - }) - .unwrap_or_default(); - - let sourceset = get_sourceset_for(sources, project_root); - - let extra_args = config - .and_then(|exe| exe.extra_args.as_ref()) - .map(|args| args.iter().map(|arg| Argument::from(*arg)).collect()) - .unwrap_or_default(); - - ExecutableModel { - executable_name, - sourceset, - extra_args, - } -} - fn assemble_modules_model<'a>( config: Option>, project_root: &Path, @@ -346,6 +319,47 @@ fn assemble_module_implementation_model<'a>( } } + +fn assemble_targets_model<'a>(targets: HashMap<&str, TargetAttribute<'a>>, absolute_project_root: &Path) -> HashMap, TargetModel<'a>> { + todo!() +} + +//noinspection ALL +fn assemble_executable_model<'a>( + project_name: Cow<'a, str>, + config: Option>, + project_root: &Path, +) -> ExecutableModel<'a> { + let config = config.as_ref(); + + let executable_name = config + .and_then(|exe| exe.executable_name) + .map(Cow::Borrowed) + .unwrap_or(project_name); + + let sources = config + .and_then(|exe| exe.sources.as_ref()) + .map(|srcs| { + srcs.iter() + .map(|src| Cow::Borrowed(*src)) + .collect::>>() + }) + .unwrap_or_default(); + + let sourceset = get_sourceset_for(sources, project_root); + + let extra_args = config + .and_then(|exe| exe.extra_args.as_ref()) + .map(|args| args.iter().map(|arg| Argument::from(*arg)).collect()) + .unwrap_or_default(); + + ExecutableModel { + executable_name, + sourceset, + extra_args, + } +} + fn assemble_tests_model<'a>( project_name: Cow<'_, str>, config: Option>, @@ -459,11 +473,6 @@ mod test { build: BuildModel { output_dir: abs_path_for_mock.join("out"), }, - executable: ExecutableModel { - executable_name: "Zork++".into(), - sourceset: SourceSet { sources: vec![] }, - extra_args: vec![], - }, modules: ModulesModel { base_ifcs_dir: Cow::default(), interfaces: vec![], @@ -471,6 +480,12 @@ mod test { implementations: vec![], sys_modules: vec![], }, + targets: Default::default(), // TODO: + executable: ExecutableModel { + executable_name: "Zork++".into(), + sourceset: SourceSet { sources: vec![] }, + extra_args: vec![], + }, tests: TestsModel { test_executable_name: "Zork++_test".into(), sourceset: SourceSet { sources: vec![] }, @@ -508,11 +523,6 @@ mod test { build: BuildModel { output_dir: abs_path_for_mock.clone(), }, - executable: ExecutableModel { - executable_name: "zork".into(), - sourceset: SourceSet { sources: vec![] }, - extra_args: vec![Argument::from("-Werr")], - }, modules: ModulesModel { base_ifcs_dir: Cow::Borrowed(Path::new("ifcs")), interfaces: vec![ @@ -553,6 +563,12 @@ mod test { ..Default::default() }], }, + executable: ExecutableModel { + executable_name: "zork".into(), + sourceset: SourceSet { sources: vec![] }, + extra_args: vec![Argument::from("-Werr")], + }, + targets: Default::default(), // TODO: tests: TestsModel { test_executable_name: "zork_check".into(), sourceset: SourceSet { sources: vec![] }, From 11175a022c1a0a4b651aa27a4325377305e7c88b Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 24 Jul 2024 07:38:13 +0200 Subject: [PATCH 50/73] feat(WIP): Splitting modules from linker command lines. Saving the job so far --- zork++/Cargo.lock | 28 ++- zork++/Cargo.toml | 3 +- zork++/src/lib/cache/mod.rs | 84 ++++--- zork++/src/lib/cli/output/executors.rs | 201 +++++++++------- zork++/src/lib/compiler/mod.rs | 114 +++++---- zork++/src/lib/config_file/mod.rs | 28 ++- zork++/src/lib/domain/commands/arguments.rs | 13 + .../src/lib/domain/commands/command_lines.rs | 33 ++- zork++/src/lib/domain/target.rs | 30 ++- zork++/src/lib/domain/translation_unit.rs | 2 +- zork++/src/lib/lib.rs | 105 +++++++-- zork++/src/lib/project_model/executable.rs | 29 --- zork++/src/lib/project_model/mod.rs | 12 +- zork++/src/lib/project_model/sourceset.rs | 2 +- zork++/src/lib/project_model/target.rs | 5 +- zork++/src/lib/project_model/tests.rs | 29 --- zork++/src/lib/utils/constants.rs | 26 +- zork++/src/lib/utils/reader.rs | 222 ++++++------------ 18 files changed, 501 insertions(+), 465 deletions(-) delete mode 100644 zork++/src/lib/project_model/executable.rs delete mode 100644 zork++/src/lib/project_model/tests.rs diff --git a/zork++/Cargo.lock b/zork++/Cargo.lock index 60c3490d..ae0a8770 100644 --- a/zork++/Cargo.lock +++ b/zork++/Cargo.lock @@ -214,7 +214,7 @@ checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "bitflags 1.3.2", "clap_lex 0.2.4", - "indexmap", + "indexmap 1.9.3", "textwrap", ] @@ -388,6 +388,12 @@ dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "erased-serde" version = "0.4.5" @@ -452,6 +458,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "heck" version = "0.5.0" @@ -509,7 +521,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", + "serde", ] [[package]] @@ -1123,6 +1146,7 @@ dependencies = [ "criterion", "env_logger", "glob", + "indexmap 2.2.6", "log", "regex", "serde", diff --git a/zork++/Cargo.toml b/zork++/Cargo.toml index 76a3e075..b1c6bd95 100644 --- a/zork++/Cargo.toml +++ b/zork++/Cargo.toml @@ -18,7 +18,8 @@ path = "src/bin/main.rs" toml = "0.5.11" glob = "0.3.1" serde = { version = "1.0.202", features = ["derive"] } -typetag = "0.2" +indexmap = {version = "2.2.6", features = ["serde"]} +typetag = {version = "0.2"} transient = "0.4.0" clap = { version = "4.0.32", features = ["derive"] } log = "0.4.17" diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 2d0e77ec..7eee470e 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -112,7 +112,9 @@ impl<'a> ZorkCache<'a> { match translation_unit_kind { TranslationUnitKind::ModuleInterface => self.get_module_ifc_cmd(translation_unit), TranslationUnitKind::ModuleImplementation => self.get_module_impl_cmd(translation_unit), - TranslationUnitKind::SourceFile(for_target) => self.get_source_cmd(translation_unit, for_target), + TranslationUnitKind::SourceFile(for_target) => { + self.get_source_cmd(translation_unit, for_target) + } TranslationUnitKind::SystemHeader => self.get_system_module_cmd(translation_unit), TranslationUnitKind::ModularStdLib(stdlib_mode) => match stdlib_mode { StdLibMode::Cpp => self.get_cpp_stdlib_cmd(), @@ -126,6 +128,7 @@ impl<'a> ZorkCache<'a> { module_interface: &T, ) -> Option<&mut SourceCommandLine<'a>> { self.generated_commands + .modules .interfaces .iter_mut() .find(|cached_tu| module_interface.path().eq(&cached_tu.path())) @@ -136,6 +139,7 @@ impl<'a> ZorkCache<'a> { module_impl: &T, ) -> Option<&mut SourceCommandLine<'a>> { self.generated_commands + .modules .implementations .iter_mut() .find(|cached_tu| module_impl.path().eq(&cached_tu.path())) @@ -144,15 +148,17 @@ impl<'a> ZorkCache<'a> { fn get_source_cmd>( &mut self, source: &T, - for_target: &TargetIdentifier<'a> + for_target: &TargetIdentifier<'a>, ) -> Option<&mut SourceCommandLine<'a>> { self.generated_commands .targets .get_mut(for_target) - .expect(error_messages::FAILURE_TARGET_SOURCES) - .sources - .iter_mut() - .find(|cached_tu| source.path().eq(&cached_tu.path())) + .and_then(|target| { + target + .sources + .iter_mut() + .find(|cached_tu| source.path().eq(&cached_tu.path())) + }) } /// Gets the target [`SystemModule`] generated [`SourceCommandLine`] from the cache @@ -164,6 +170,7 @@ impl<'a> ZorkCache<'a> { system_module: &T, ) -> Option<&mut SourceCommandLine<'a>> { self.generated_commands + .modules .system_modules .iter_mut() .find(|cached_tu| system_module.file_stem().eq(cached_tu.filename())) @@ -174,8 +181,8 @@ impl<'a> ZorkCache<'a> { stdlib_mode: StdLibMode, ) -> Option<&mut SourceCommandLine<'a>> { match stdlib_mode { - StdLibMode::Cpp => self.generated_commands.cpp_stdlib.as_mut(), - StdLibMode::CCompat => self.generated_commands.c_compat_stdlib.as_mut(), + StdLibMode::Cpp => self.generated_commands.modules.cpp_stdlib.as_mut(), + StdLibMode::CCompat => self.generated_commands.modules.c_compat_stdlib.as_mut(), } } @@ -185,16 +192,16 @@ impl<'a> ZorkCache<'a> { cmd_line: Option>, ) { match stdlib_mode { - StdLibMode::Cpp => self.generated_commands.cpp_stdlib = cmd_line, - StdLibMode::CCompat => self.generated_commands.c_compat_stdlib = cmd_line, + StdLibMode::Cpp => self.generated_commands.modules.cpp_stdlib = cmd_line, + StdLibMode::CCompat => self.generated_commands.modules.c_compat_stdlib = cmd_line, } } fn get_cpp_stdlib_cmd(&mut self) -> Option<&mut SourceCommandLine<'a>> { - self.generated_commands.cpp_stdlib.as_mut() + self.generated_commands.modules.cpp_stdlib.as_mut() } fn get_ccompat_stdlib_cmd(&mut self) -> Option<&mut SourceCommandLine<'a>> { - self.generated_commands.c_compat_stdlib.as_mut() + self.generated_commands.modules.c_compat_stdlib.as_mut() } /// The tasks associated with the cache after load it from the file system @@ -249,13 +256,19 @@ impl<'a> ZorkCache<'a> { let generated_commands = &self.generated_commands; generated_commands + .modules .cpp_stdlib .as_slice() .iter() - .chain(generated_commands.c_compat_stdlib.as_slice().iter()) - .chain(generated_commands.interfaces.iter()) - .chain(generated_commands.implementations.iter()) - // TODO: .chain(generated_commands.sources.iter()) + .chain(generated_commands.modules.c_compat_stdlib.as_slice().iter()) + .chain(generated_commands.modules.interfaces.iter()) + .chain(generated_commands.modules.implementations.iter()) + .chain( + generated_commands + .targets + .values() + .flat_map(|target| target.sources.iter()), + ) } /// The current integer value that is the total of commands generated for all the @@ -264,11 +277,11 @@ impl<'a> ZorkCache<'a> { pub fn count_total_generated_commands(&self) -> usize { let latest_commands = &self.generated_commands; - latest_commands.interfaces.len() - + latest_commands.implementations.len() - // + latest_commands.sources.len() // TODO: - + latest_commands.system_modules.len() + latest_commands.modules.interfaces.len() + + latest_commands.modules.implementations.len() + + latest_commands.modules.system_modules.len() + 2 // the cpp_stdlib and the c_compat_stdlib + + latest_commands.targets.values().flat_map(|target| target.sources.iter()).count() } } @@ -448,7 +461,6 @@ mod msvc { mod helpers { use self::utils::constants::error_messages; use super::*; - use crate::cli::input::Command; use crate::domain::translation_unit::TranslationUnitStatus; use std::path::PathBuf; @@ -486,25 +498,27 @@ mod helpers { pub(crate) fn check_user_files_removals( cache: &mut ZorkCache, program_data: &ZorkModel<'_>, - cli_args: &CliArgs, + _cli_args: &CliArgs, ) -> bool { remove_if_needed_from_cache_and_count_changes( - &mut cache.generated_commands.interfaces, + &mut cache.generated_commands.modules.interfaces, &program_data.modules.interfaces, ) || remove_if_needed_from_cache_and_count_changes( - &mut cache.generated_commands.implementations, + &mut cache.generated_commands.modules.implementations, &program_data.modules.implementations, - ) - /* || - remove_if_needed_from_cache_and_count_changes( - &mut cache.generated_commands.sources, - if !cli_args.command.eq(&Command::Test) { - &program_data.executable.sourceset.sources - } else { - &program_data.tests.sourceset.sources - }, ) */ - || remove_if_needed_from_cache_and_count_changes( - &mut cache.generated_commands.system_modules, + ) || { + for (target_name, target_data) in cache.generated_commands.targets.iter_mut() { + let changes = remove_if_needed_from_cache_and_count_changes( + &mut target_data.sources, + &program_data.targets.get(target_name).unwrap(/*TODO:*/).sources.sources, + ); + if changes { + return true; + } + } + return false; + } || remove_if_needed_from_cache_and_count_changes( + &mut cache.generated_commands.modules.system_modules, &program_data.modules.sys_modules, ) } diff --git a/zork++/src/lib/cli/output/executors.rs b/zork++/src/lib/cli/output/executors.rs index 86b13db1..ecbfee21 100644 --- a/zork++/src/lib/cli/output/executors.rs +++ b/zork++/src/lib/cli/output/executors.rs @@ -6,7 +6,8 @@ use std::{path::Path, process::ExitStatus}; use crate::cache::EnvVars; use crate::domain::commands::arguments::{Argument, Arguments}; -use crate::domain::commands::command_lines::SourceCommandLine; +use crate::domain::commands::command_lines::ModulesCommands; +use crate::domain::target::{Target, TargetIdentifier}; use crate::domain::translation_unit::TranslationUnitStatus; use crate::utils::constants::error_messages; use crate::{ @@ -19,94 +20,62 @@ use color_eyre::{ eyre::{eyre, Context}, Report, Result, }; +use indexmap::IndexMap; -pub fn run_generated_commands<'a>( +pub fn run_modules_generated_commands<'a> ( program_data: &ZorkModel<'a>, - cache: &mut ZorkCache<'a>, -) -> Result<()> { - log::info!("Proceeding to execute the generated commands..."); - - let generated_commands = &mut cache.generated_commands; - - let general_args = generated_commands - .general_args - .as_mut() - .expect(error_messages::GENERAL_ARGS_NOT_FOUND) - .get_args(); + general_args: &Arguments<'a>, + compiler_specific_shared_args: &Arguments, + modules_generated_commands: &mut ModulesCommands<'a>, + env_vars: &EnvVars, +)-> Result<()> { + log::info!("Proceeding to execute the generated modules commands..."); - let compiler_specific_shared_args = generated_commands - .compiler_common_args - .as_mut() - .with_context(|| error_messages::COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND)? - .get_args(); + // Process the modules + helpers::process_modules_commands( + program_data, + general_args, + compiler_specific_shared_args, + modules_generated_commands, + env_vars, + ) +} - let env_vars = match program_data.compiler.cpp_compiler { - CppCompiler::MSVC => &cache.compilers_metadata.msvc.env_vars, - CppCompiler::CLANG => &cache.compilers_metadata.clang.env_vars, - CppCompiler::GCC => &cache.compilers_metadata.gcc.env_vars, - }; +pub fn run_targets_generated_commands<'a>( + program_data: &ZorkModel<'a>, + general_args: &Arguments<'a>, + compiler_specific_shared_args: &Arguments, + targets: &mut IndexMap, + env_vars: &EnvVars, +) -> Result<()> { + log::info!("Proceeding to execute the generated commands..."); - let mut mock = vec![]; - - let translation_units_commands: Vec<&mut SourceCommandLine> = - helpers::get_translation_units_commands( - // Independent borrows to avoid have borrow checker yielding at me - &mut generated_commands.cpp_stdlib, - &mut generated_commands.c_compat_stdlib, - &mut generated_commands.system_modules, - &mut generated_commands.interfaces, - &mut generated_commands.implementations, - // &mut generated_commands.sources, - &mut mock, + // Process the user declared targets TODO: Filtered by cli? + // TODO: avoid the callee of the autorun binary by decoupling modules from linkers execs + for (target_name, target_data) in targets { + log::info!( + "Executing the linker command line for target: {:?}", + target_name ); - - let compile_but_dont_link: [Argument; 1] = - [Argument::from(match program_data.compiler.cpp_compiler { - CppCompiler::CLANG | CppCompiler::GCC => "-c", - CppCompiler::MSVC => "/c", - })]; - - for translation_unit_cmd in translation_units_commands { - // Join the concrete args of any translation unit with the ones held in the flyweights - let translation_unit_cmd_args: Arguments = general_args - .iter() - .chain(compiler_specific_shared_args.iter()) - .chain(&compile_but_dont_link) - .chain(translation_unit_cmd.args.iter()) - .collect(); - - let r = execute_command(program_data, &translation_unit_cmd_args, env_vars); - translation_unit_cmd.status = TranslationUnitStatus::from(&r); + let r = helpers::execute_linker_command_line( + program_data, + general_args, + compiler_specific_shared_args, + &target_data.linker, + env_vars, + target_data + ); + target_data.linker.execution_result = TranslationUnitStatus::from(&r); if let Err(e) = r { return Err(e); } else if !r.as_ref().unwrap().success() { - let err = eyre!( - "Ending the program, because the build of: {:?} failed", - translation_unit_cmd.filename - ); - return Err(err); + return Err(eyre!( + "Ending the program, because the linker command line execution failed", + )); } } - /* log::info!("Processing the linker command line..."); - let r = helpers::execute_linker_command_line( - program_data, - general_args, - compiler_specific_shared_args, - &generated_commands.linker, - env_vars, - ); - cache.generated_commands.linker.execution_result = TranslationUnitStatus::from(&r); - - if let Err(e) = r { - return Err(e); - } else if !r.as_ref().unwrap().success() { - return Err(eyre!( - "Ending the program, because the linker command line execution failed", - )); - } */ - Ok(()) } @@ -120,6 +89,7 @@ pub fn autorun_generated_binary( let args = &[Argument::from( output_dir .join(compiler.as_ref()) + // TODO: join with the correct value .join(executable_name) .with_extension(constants::BINARY_EXTENSION), )]; @@ -169,38 +139,102 @@ where mod helpers { use crate::cache::EnvVars; use crate::cli::output::executors::execute_command; - use crate::domain::commands::arguments::Arguments; - use crate::domain::commands::command_lines::{LinkerCommandLine, SourceCommandLine}; + use crate::domain::commands::arguments::{Argument, Arguments}; + use crate::domain::commands::command_lines::{ + LinkerCommandLine, ModulesCommands, SourceCommandLine, + }; + use crate::domain::target::Target; use crate::domain::translation_unit::TranslationUnitStatus; + use crate::project_model::compiler::CppCompiler; use crate::project_model::ZorkModel; - use color_eyre::eyre::Result; + + use color_eyre::eyre::{eyre, Result}; use std::process::ExitStatus; pub(crate) fn execute_linker_command_line( program_data: &ZorkModel, - general_args: Arguments, - compiler_specific_shared_args: Arguments, + general_args: &Arguments, + compiler_specific_shared_args: &Arguments, linker_command_line: &LinkerCommandLine, env_vars: &EnvVars, + target_data: &Target ) -> Result { let linker_args = linker_command_line.get_target_output_for(program_data.compiler.cpp_compiler); + + let linker_sources_byproducts = target_data.sources.iter().map(|scl| &scl.byproduct); + // let modules_sources_byproducts = + let args = general_args .iter() .chain(linker_args.iter()) .chain(compiler_specific_shared_args.iter()) .chain(linker_command_line.byproducts.iter()) + .chain(linker_sources_byproducts) .collect::(); execute_command(program_data, &args, env_vars) } - pub(crate) fn get_translation_units_commands<'a, 'b>( + pub(crate) fn process_modules_commands<'a>( + program_data: &ZorkModel<'a>, + general_args: &Arguments, + compiler_specific_shared_args: &Arguments, + generated_commands: &mut ModulesCommands<'a>, + env_vars: &std::collections::HashMap, + ) -> Result<()> { + let translation_units_commands: Vec<&mut SourceCommandLine> = + get_modules_translation_units_commands( + // Independent borrows to avoid have borrow checker yielding at me + &mut generated_commands.cpp_stdlib, + &mut generated_commands.c_compat_stdlib, + &mut generated_commands.system_modules, + &mut generated_commands.interfaces, + &mut generated_commands.implementations, + ); + + if translation_units_commands.is_empty() { + log::debug!("No modules to process, build or rebuild in this iteration."); + return Ok(()); + } + + let compile_but_dont_link: [Argument; 1] = + [Argument::from(match program_data.compiler.cpp_compiler { + CppCompiler::CLANG | CppCompiler::GCC => "-c", + CppCompiler::MSVC => "/c", + })]; + + for translation_unit_cmd in translation_units_commands { + // Join the concrete args of any translation unit with the ones held in the flyweights + let translation_unit_cmd_args: Arguments = general_args + .iter() + .chain(compiler_specific_shared_args.iter()) + .chain(&compile_but_dont_link) + .chain(translation_unit_cmd.args.iter()) + .collect(); + + let r = execute_command(program_data, &translation_unit_cmd_args, env_vars); + translation_unit_cmd.status = TranslationUnitStatus::from(&r); + + if let Err(e) = r { + return Err(e); + } else if !r.as_ref().unwrap().success() { + let err = eyre!( + "Ending the program, because the build of: {:?} failed", + translation_unit_cmd.filename + ); + return Err(err); + } + } + Ok(()) + } + + /// TODO: create strong types or aliases at least + pub(crate) fn get_modules_translation_units_commands<'a, 'b>( cpp_stdlib: &'b mut Option>, c_compat_stdlib: &'b mut Option>, system_modules: &'b mut Vec>, interfaces: &'b mut Vec>, implementations: &'b mut Vec>, - sources: &'b mut Vec>, ) -> Vec<&'b mut SourceCommandLine<'a>> { cpp_stdlib .as_mut_slice() @@ -209,7 +243,6 @@ mod helpers { .chain(system_modules.as_mut_slice().iter_mut()) .chain(interfaces.as_mut_slice().iter_mut()) .chain(implementations.as_mut_slice().iter_mut()) - .chain(sources.as_mut_slice().iter_mut()) .filter(|scl| scl.status.eq(&TranslationUnitStatus::PendingToBuild)) .collect::>() } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 410e58cc..b5788241 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -12,14 +12,12 @@ use crate::domain::commands::arguments::Argument; use crate::domain::target::TargetIdentifier; use crate::domain::translation_unit::TranslationUnitStatus; use crate::project_model::modules::SystemModule; +use crate::project_model::target::TargetModel; use crate::utils::constants::error_messages; use crate::{ cache::ZorkCache, - cli::input::{CliArgs, Command}, - domain::{ - target::ExecutableTarget, - translation_unit::{TranslationUnit, TranslationUnitKind}, - }, + cli::input::CliArgs, + domain::translation_unit::{TranslationUnit, TranslationUnitKind}, project_model::{ compiler::{CppCompiler, StdLibMode}, modules::{ModuleImplementationModel, ModuleInterfaceModel}, @@ -144,9 +142,9 @@ fn process_targets<'a>( ) -> Result<()> { for target in &model.targets { // 2nd - Generate the commands for the non-module sources - generate_sources_cmds_args(model, cache, cli_args)?; + generate_sources_cmds_args(model, cache, cli_args, target)?; // 3rd - Generate the linker command for the 'target' declared by the user - generate_linkage_targets_commands(model, cache, cli_args); + generate_linkage_targets_commands(model, cache, cli_args, target); } Ok(()) } @@ -162,46 +160,38 @@ fn generate_sources_cmds_args<'a>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, cli_args: &'a CliArgs, + target: (&'a TargetIdentifier<'a>, &'a TargetModel<'a>), ) -> Result<()> { - log::info!("Generating the commands for the source files..."); + log::info!( + "Generating the commands for the source files of target: {:?}", + &target.0 + ); - let is_tests_run = cli_args.command.eq(&Command::Test); - - let srcs = if is_tests_run { - &model.tests.sourceset.sources - } else { - &model.executable.sourceset.sources - }; + let target_identifier = target.0; + let target_data = target.1; process_kind_translation_units( model, cache, cli_args, - srcs, - TranslationUnitKind::SourceFile(TargetIdentifier::default()), // TODO: + &target_data.sources.sources, + TranslationUnitKind::SourceFile(target_identifier), ) .with_context(|| error_messages::FAILURE_TARGET_SOURCES) } -/// Generates the command line that will be passed to the linker to generate an [`ExecutableTarget`] -/// Generates the commands for the C++ modules declared in the project -/// -/// Legacy: -/// If this flow is enabled by the Cli arg `Tests`, then the executable will be generated -/// for the files and properties declared for the tests section in the configuration file +/// Generates the command line that will be passed to the linker to generate an target final product fn generate_linkage_targets_commands<'a>( model: &'a ZorkModel<'_>, cache: &mut ZorkCache<'a>, - cli_args: &'a CliArgs, + _cli_args: &'a CliArgs, + target: (&'a TargetIdentifier<'a>, &'a TargetModel<'a>), ) { - // TODO: Shouldn't we start to think about named targets? So introduce the static and dynamic - // libraries wouldn't be such a pain? - let is_tests_run = cli_args.command.eq(&Command::Test); - if is_tests_run { - generate_linker_general_command_line_args(model, cache, &model.tests); - } else { - generate_linker_general_command_line_args(model, cache, &model.executable); - } + log::info!( + "Generating the linker command line for target: {:?}", + &target.0 + ); + generate_linker_general_command_line_args(model, cache, target); } /// Generates the general command line arguments for the desired target @@ -212,11 +202,17 @@ fn generate_linkage_targets_commands<'a>( pub fn generate_linker_general_command_line_args<'a>( model: &ZorkModel<'_>, cache: &mut ZorkCache<'a>, - target: &'a impl ExecutableTarget<'a>, + target: (&'a TargetIdentifier<'a>, &'a TargetModel<'a>), ) { - log::info!("Generating the linker command line..."); + let target_identifier = target.0; + let target_details = target.1; - let linker = &mut cache.generated_commands.targets.get_mut(&TargetIdentifier::default()).unwrap().linker; // TODO: + let linker = &mut cache + .generated_commands + .targets + .get_mut(target_identifier) + .unwrap() + .linker; let compiler = &model.compiler.cpp_compiler; let out_dir: &Path = model.build.output_dir.as_ref(); @@ -224,7 +220,7 @@ pub fn generate_linker_general_command_line_args<'a>( let target_output = Argument::from( out_dir .join(compiler.as_ref()) - .join(target.name()) + .join(target_identifier.value().as_ref()) .with_extension(constants::BINARY_EXTENSION), ); @@ -235,9 +231,11 @@ pub fn generate_linker_general_command_line_args<'a>( }; } - if Iterator::ne(linker.extra_args.iter(), target.extra_args().iter()) { + if Iterator::ne(linker.extra_args.iter(), target_details.extra_args.iter()) { linker.extra_args.clear(); - linker.extra_args.extend_from_slice(target.extra_args()); + linker + .extra_args + .extend_from_to_argument_slice(&target_details.extra_args); } } @@ -265,7 +263,7 @@ fn process_kind_translation_units<'a, T: TranslationUnit<'a>>( fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, - cli_args: &'a CliArgs, + _cli_args: &'a CliArgs, translation_unit: &'a T, for_kind: &TranslationUnitKind<'a>, ) -> Result<()> { @@ -301,21 +299,10 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( modules::generate_module_implementation_cmd(model, cache, resolved_tu) } TranslationUnitKind::SourceFile(related_target) => { - let target = if cli_args.command.eq(&Command::Test) { - &model.tests as &dyn ExecutableTarget - } else { - &model.executable as &dyn ExecutableTarget - }; let resolved_tu = transient::Downcast::downcast_ref::(tu_with_erased_type) .with_context(|| helpers::wrong_downcast_msg(translation_unit))?; - sources::generate_sources_arguments( - model, - cache, - resolved_tu, - &related_target, - target, - )?; + sources::generate_sources_arguments(model, cache, resolved_tu, &related_target)?; } TranslationUnitKind::SystemHeader => { let resolved_tu = @@ -332,12 +319,12 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( /// Command line arguments generators procedures for C++ standard modules mod modules { - use std::path::{Path, PathBuf}; + use std::path::{Path}; use crate::cache::ZorkCache; use crate::compiler::helpers; use crate::compiler::helpers::generate_bmi_file_path; - use crate::domain::commands::arguments::{clang_args, msvc_args, Arguments}; + use crate::domain::commands::arguments::{clang_args, msvc_args, Argument, Arguments}; use crate::domain::commands::command_lines::SourceCommandLine; use crate::domain::translation_unit::{TranslationUnit, TranslationUnitStatus}; use crate::project_model::compiler::{CppCompiler, StdLibMode}; @@ -411,7 +398,7 @@ mod modules { arguments.push(interface.path()); let cmd_line = SourceCommandLine::new(interface, arguments, binary_module_ifc); - cache.generated_commands.interfaces.push(cmd_line); + cache.generated_commands.modules.interfaces.push(cmd_line); } /// Generates the required arguments for compile the implementation module files @@ -454,7 +441,7 @@ mod modules { } let cmd = SourceCommandLine::new(implementation.to_owned(), arguments, obj_file_path); - cache.generated_commands.implementations.push(cmd); + cache.generated_commands.modules.implementations.push(cmd); } /// System headers can be imported as modules, but they must be built before being imported. @@ -497,9 +484,9 @@ mod modules { filename: sys_module.to_string(), args, status: TranslationUnitStatus::PendingToBuild, - byproduct: /* TODO:!!!!!!: */ PathBuf::default() + byproduct: /* TODO:!!!!!!: */ Argument::default() }; - cache.generated_commands.system_modules.push(cmd); + cache.generated_commands.modules.system_modules.push(cmd); } pub(crate) fn generate_modular_cpp_stdlib_args<'a>( @@ -532,9 +519,10 @@ mod sources { use crate::cache::ZorkCache; use crate::domain::commands::arguments::Arguments; use crate::domain::commands::command_lines::SourceCommandLine; - use crate::domain::target::{ExecutableTarget, TargetIdentifier}; + use crate::domain::target::{TargetIdentifier}; use crate::domain::translation_unit::TranslationUnit; use crate::project_model::sourceset::SourceFile; + use crate::project_model::target::TargetModel; use crate::project_model::{compiler::CppCompiler, ZorkModel}; use crate::utils::constants::error_messages; use color_eyre::eyre::{ContextCompat, Result}; @@ -547,13 +535,16 @@ mod sources { cache: &mut ZorkCache<'a>, source: &'a SourceFile<'a>, target_identifier: &TargetIdentifier<'a>, - target: &'a (impl ExecutableTarget<'a> + ?Sized), ) -> Result<()> { let compiler = model.compiler.cpp_compiler; let out_dir = model.build.output_dir.as_ref(); + let target: &TargetModel<'_> = model + .targets + .get(target_identifier) + .with_context(|| error_messages::FAILURE_FINDING_TARGET)?; let mut arguments = Arguments::default(); - arguments.extend_from_slice(target.extra_args()); + arguments.extend_from_to_argument_slice(&target.extra_args); let obj_file = helpers::generate_obj_file(compiler, out_dir, source); let fo = if compiler.eq(&CppCompiler::MSVC) { @@ -564,6 +555,9 @@ mod sources { arguments.push(format!("{fo}{}", obj_file.display())); arguments.push(source.path()); + log::warn!("Adding target entry for: {:?}", target_identifier); + log::warn!("Targets status: {:?}", cache.generated_commands.targets); + let command_line = SourceCommandLine::new(source, arguments, obj_file); cache .generated_commands diff --git a/zork++/src/lib/config_file/mod.rs b/zork++/src/lib/config_file/mod.rs index 256edeb7..d32f0fb2 100644 --- a/zork++/src/lib/config_file/mod.rs +++ b/zork++/src/lib/config_file/mod.rs @@ -1,20 +1,20 @@ //! root file for the crate where the datastructures that holds the TOML //! parsed data lives. -pub mod project; -pub mod compiler; pub mod build; +pub mod compiler; +pub mod executable; pub mod modules; +pub mod project; pub mod target; -pub mod executable; pub mod tests; -use std::{collections::HashMap, fmt::Debug}; - -use serde::{Deserialize, Serialize}; +use indexmap::IndexMap; +use serde::{Deserialize, Deserializer, Serialize}; use self::{ build::BuildAttribute, compiler::CompilerAttribute, executable::ExecutableAttribute, - modules::ModulesAttribute, target::TargetAttribute, project::ProjectAttribute, tests::TestsAttribute + modules::ModulesAttribute, project::ProjectAttribute, target::TargetAttribute, + tests::TestsAttribute, }; /// ```rust @@ -91,14 +91,24 @@ pub struct ZorkConfigFile<'a> { pub build: Option>, #[serde(borrow)] pub modules: Option>, - #[serde(borrow)] - pub targets: HashMap<&'a str, TargetAttribute<'a>>, + #[serde(deserialize_with = "deserialize_targets")] + pub targets: IndexMap<&'a str, TargetAttribute<'a>>, #[serde(borrow)] pub executable: Option>, #[serde(borrow)] pub tests: Option>, } +fn deserialize_targets<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + let helper: IndexMap<&str, TargetAttribute> = Deserialize::deserialize(deserializer)?; + Ok(helper) +} + pub fn zork_cfg_from_file(cfg: &'_ str) -> Result, toml::de::Error> { ::deserialize(&mut toml::Deserializer::new(cfg)) } diff --git a/zork++/src/lib/domain/commands/arguments.rs b/zork++/src/lib/domain/commands/arguments.rs index 17eddef6..25eef6ae 100644 --- a/zork++/src/lib/domain/commands/arguments.rs +++ b/zork++/src/lib/domain/commands/arguments.rs @@ -148,6 +148,19 @@ impl<'a> Arguments<'a> { self.0.extend_from_slice(slice); } + /// Extends the underlying collection given a slice of any type that is convertible to [`Argument`] + /// and clonable + pub fn extend_from_to_argument_slice(&mut self, slice: &'a [F]) + where + F: Into> + Clone, + { + self.0.extend( + slice + .iter() + .map(|arg| Argument::from(>::into(arg.clone()))), + ); + } + pub fn as_slice(&self) -> &[Argument] { &self.0 } diff --git a/zork++/src/lib/domain/commands/command_lines.rs b/zork++/src/lib/domain/commands/command_lines.rs index d9acaf43..66972834 100644 --- a/zork++/src/lib/domain/commands/command_lines.rs +++ b/zork++/src/lib/domain/commands/command_lines.rs @@ -5,6 +5,7 @@ use crate::domain::translation_unit::{TranslationUnit, TranslationUnitStatus}; use crate::project_model::compiler::CppCompiler; use crate::utils::constants::error_messages; use color_eyre::eyre::{ContextCompat, Result}; +use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Debug; @@ -25,17 +26,17 @@ pub struct SourceCommandLine<'a> { pub filename: String, pub args: Arguments<'a>, pub status: TranslationUnitStatus, - pub byproduct: PathBuf, + pub byproduct: Argument<'a>, } impl<'a> SourceCommandLine<'a> { - pub fn new>(tu: &T, args: Arguments<'a>, byproduct: PathBuf) -> Self { + pub fn new, B: Into>>(tu: &T, args: Arguments<'a>, byproduct: B) -> Self { Self { directory: PathBuf::from(tu.parent()), filename: tu.filename(), args, status: TranslationUnitStatus::PendingToBuild, - byproduct, + byproduct: byproduct.into(), } } @@ -49,10 +50,10 @@ impl<'a> SourceCommandLine<'a> { } #[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)] - pub struct LinkerCommandLine<'a> { - link_modules: bool, + link_modules: bool, // TODO: pending pub target: Argument<'a>, + pub modules_byproducts: Arguments<'a>, pub byproducts: Arguments<'a>, pub extra_args: Arguments<'a>, pub execution_result: TranslationUnitStatus, @@ -81,12 +82,17 @@ impl<'a> LinkerCommandLine<'a> { pub struct Commands<'a> { pub general_args: Option>, pub compiler_common_args: Option>, + pub modules: ModulesCommands<'a>, + pub targets: IndexMap, Target<'a>>, +} + +#[derive(Serialize, Deserialize, Default, Debug)] +pub struct ModulesCommands<'a> { pub cpp_stdlib: Option>, pub c_compat_stdlib: Option>, pub system_modules: Vec>, pub interfaces: Vec>, pub implementations: Vec>, - pub targets: HashMap, Target<'a>>, } impl<'a> Commands<'a> { @@ -95,19 +101,20 @@ impl<'a> Commands<'a> { /// standard libraries implementations (see: [crate::project_model::compiler::StdLibMode]) /// joined to all the commands generated for every [TranslationUnit] declared by the user for /// its project - pub fn get_all_command_lines( + pub fn get_all_modules_command_lines( &mut self, ) -> impl Iterator> + Debug { - self.cpp_stdlib + self.modules + .cpp_stdlib .as_mut_slice() .iter_mut() - .chain(self.c_compat_stdlib.as_mut_slice().iter_mut()) - .chain(self.system_modules.as_mut_slice().iter_mut()) - .chain(self.interfaces.as_mut_slice().iter_mut()) - .chain(self.implementations.as_mut_slice().iter_mut()) - // TODO: .chain(self.sources.as_mut_slice().iter_mut()) + .chain(self.modules.c_compat_stdlib.as_mut_slice().iter_mut()) + .chain(self.modules.system_modules.as_mut_slice().iter_mut()) + .chain(self.modules.interfaces.as_mut_slice().iter_mut()) + .chain(self.modules.implementations.as_mut_slice().iter_mut()) } + // TODO: unused, think that doesn't makes sense anymore with the current architechture pub fn add_linker_file_path( &mut self, target_identifier: &TargetIdentifier<'a>, diff --git a/zork++/src/lib/domain/target.rs b/zork++/src/lib/domain/target.rs index a7ef00f2..af8b0fde 100644 --- a/zork++/src/lib/domain/target.rs +++ b/zork++/src/lib/domain/target.rs @@ -15,11 +15,37 @@ pub struct Target<'a> { pub kind: TargetKind, } +impl<'a> Target<'a> { + /// Defaults initializes a new [`Target`] when the unique data + /// is the [`TargetKind`]. This is useful in our internals when there's + /// no entry for this target on the [`ZorkCache`] and we want to create a new + /// one in place to add a new [`SourceCommandLine`] + pub fn new_default_for_kind(kind: TargetKind) -> Self { + Self { + sources: Vec::default(), + linker: LinkerCommandLine::default(), + kind, + } + } +} + /// Strong type for storing the target unique identifier, which instead of being /// composite within the [`Target`] struct, is externalized in this wrapped type, so /// we can use a strong type on the [`Commands.targets`] container -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] -pub struct TargetIdentifier<'a>(Cow<'a, str>); +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash, Clone)] +pub struct TargetIdentifier<'a>(pub Cow<'a, str>); + +impl<'a> From<&'a str> for TargetIdentifier<'a> { + fn from(value: &'a str) -> Self { + Self(Cow::Borrowed(value)) + } +} + +impl<'a> TargetIdentifier<'a> { + pub fn value(&self) -> &Cow<'a, str> { + &self.0 + } +} /// The different types of final products #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Copy, Clone)] diff --git a/zork++/src/lib/domain/translation_unit.rs b/zork++/src/lib/domain/translation_unit.rs index 949b3e70..29fe7e7d 100644 --- a/zork++/src/lib/domain/translation_unit.rs +++ b/zork++/src/lib/domain/translation_unit.rs @@ -96,7 +96,7 @@ macro_rules! impl_translation_unit_for { pub enum TranslationUnitKind<'a> { ModuleInterface, ModuleImplementation, - SourceFile(TargetIdentifier<'a>), + SourceFile(&'a TargetIdentifier<'a>), ModularStdLib(StdLibMode), SystemHeader, } diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 839c2d83..50f0d2e8 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -16,6 +16,7 @@ pub mod utils; /// data sent to stdout/stderr pub mod worker { use crate::config_file::ZorkConfigFile; + use crate::domain::target::Target; use crate::project_model; use crate::{config_file, utils::fs::get_project_root_absolute_path}; use std::{fs, path::Path, time::Instant}; @@ -35,6 +36,7 @@ pub mod worker { template::create_templated_project, }, }; + use color_eyre::eyre::ContextCompat; use color_eyre::{eyre::Context, Report, Result}; /// The main work of the project. Runs the tasks @@ -85,7 +87,6 @@ pub mod worker { let program_data: ZorkModel<'_> = if config_file.last_time_modified > cache.metadata.last_program_execution { - log::debug!("Rebuilding the ZorkModel due to changes of the cfg file"); cache.metadata.save_project_model = true; utils::reader::build_model(config, cli_args, &abs_project_root)? } else { @@ -93,15 +94,32 @@ pub mod worker { project_model::load(&cache)? }; - do_main_work_based_on_cli_input(cli_args, &program_data, cache).with_context(|| { + map_model_targets_to_cache(&program_data, &mut cache); + + let generate_commands_ts = Instant::now(); + // Generates the commands for every translation unit and/or checks on successive iterations + // of the program it any of them has been modified so the files must be marked to be + // rebuilt again + generate_commands(&program_data, &mut cache, cli_args) + .with_context(|| error_messages::FAILURE_GENERATING_COMMANDS)?; + + log::debug!( + "Zork++ took a total of {:?} ms on handling the generated commands", + generate_commands_ts.elapsed().as_millis() + ); + let tmp = do_main_work_based_on_cli_input(cli_args, &program_data, cache).with_context(|| { format!( "{}: {:?}", error_messages::FAILED_BUILD_FOR_CFG_FILE, cfg_path ) })?; + + } + + Ok(()) } @@ -115,35 +133,76 @@ pub mod worker { program_data: &'a ZorkModel<'a>, mut cache: ZorkCache<'a>, ) -> Result<()> { - let generate_commands_ts = Instant::now(); - // Generates the commands for every translation unit and/or checks on successive iterations - // of the program it any of them has been modified so the files must be marked to be - // rebuilt again - generate_commands(program_data, &mut cache, cli_args) - .with_context(|| error_messages::FAILURE_GENERATING_COMMANDS)?; - log::debug!( - "Zork++ took a total of {:?} ms on handling the generated commands", - generate_commands_ts.elapsed().as_millis() - ); - let execution_result = match cli_args.command { - Command::Build => executors::run_generated_commands(program_data, &mut cache), + let generated_commands = &mut cache.generated_commands; + + let general_args = generated_commands + .general_args.clone() + .with_context(|| error_messages::GENERAL_ARGS_NOT_FOUND)? + .get_args(); + + let compiler_specific_shared_args = generated_commands + .compiler_common_args + .as_mut() + .with_context(|| error_messages::COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND)? + .get_args(); + + let env_vars = match program_data.compiler.cpp_compiler { + CppCompiler::MSVC => &cache.compilers_metadata.msvc.env_vars, + CppCompiler::CLANG => &cache.compilers_metadata.clang.env_vars, + CppCompiler::GCC => &cache.compilers_metadata.gcc.env_vars, + }; + + executors::run_modules_generated_commands(program_data, &general_args, &compiler_specific_shared_args, &mut generated_commands.modules, &env_vars)?; + // TODO: cache save by not propagating Err with ? + + match cli_args.command { + Command::Build => executors::run_targets_generated_commands(program_data, &general_args, &compiler_specific_shared_args, &mut generated_commands.targets, &env_vars), Command::Run | Command::Test => { - match executors::run_generated_commands(program_data, &mut cache) { - Ok(_) => executors::autorun_generated_binary( - &program_data.compiler.cpp_compiler, - &program_data.build.output_dir, - &program_data.executable.executable_name, - ), + // TODO: for target in cache and filtered by cmdargs + match executors::run_targets_generated_commands(program_data, &general_args, &compiler_specific_shared_args, &mut generated_commands.targets, &env_vars) { + Ok(_) => { + for (target_name, target_data) in generated_commands.targets.iter() { + executors::autorun_generated_binary( + &program_data.compiler.cpp_compiler, + &program_data.build.output_dir, + target_name.value(), + // &program_data.executable.executable_name, + )? + } + return Ok(()); + } Err(e) => Err(e), } } _ => todo!("{}", error_messages::CLI_ARGS_CMD_NEW_BRANCH), }; - cache.save(program_data, cli_args)?; - execution_result + cache.save(&program_data, cli_args) + } + + /// Helper to map the user declared targets on the [`ZorkModel`], previously mapped from the + /// [`ZorkConfigFile`] into the [`ZorkCache`], in order to avoid later calls with entry or insert + /// which will be hidden on the code an harder to read for newcomers or after time without + /// reading the codebase + fn map_model_targets_to_cache<'a>( + program_data: &ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + ) { + for (target_identifier, target_data) in program_data.targets.iter() { + if !cache + .generated_commands + .targets + .contains_key(target_identifier) + { + log::debug!("Adding a new target to the cache: {:?}", target_identifier); + cache.generated_commands.targets.insert( + target_identifier.clone(), + Target::new_default_for_kind(target_data.kind), + ); + } + } } /// Creates the directory for output the elements generated @@ -173,7 +232,7 @@ pub mod worker { let out_dir = Path::new(project_root).join(binding); if out_dir.exists() { - return Ok(()); + return Ok(()); // TODO: remeber that this causes a bug } // early guard. If the out_dir already exists, all // the sub-structure must exists and be correct. // Otherwise, a full out dir wipe will be preferable diff --git a/zork++/src/lib/project_model/executable.rs b/zork++/src/lib/project_model/executable.rs deleted file mode 100644 index a7d4fed2..00000000 --- a/zork++/src/lib/project_model/executable.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::borrow::Cow; - -use serde::{Deserialize, Serialize}; - -use super::sourceset::SourceSet; -use crate::domain::commands::arguments::Argument; -use crate::domain::target::{ExecutableTarget, ExtraArgs}; - -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct ExecutableModel<'a> { - pub executable_name: Cow<'a, str>, - pub sourceset: SourceSet<'a>, - pub extra_args: Vec>, -} - -impl<'a> ExtraArgs<'a> for ExecutableModel<'a> { - fn extra_args(&'a self) -> &'a [Argument] { - &self.extra_args - } -} - -impl<'a> ExecutableTarget<'a> for ExecutableModel<'a> { - fn name(&'a self) -> &'a str { - self.executable_name.as_ref() - } - fn sourceset(&'a self) -> &'a SourceSet { - &self.sourceset - } -} diff --git a/zork++/src/lib/project_model/mod.rs b/zork++/src/lib/project_model/mod.rs index 70081c91..655d5e0c 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -1,16 +1,15 @@ pub mod build; pub mod compiler; -pub mod executable; pub mod modules; pub mod project; pub mod sourceset; pub mod target; -pub mod tests; -use std::{collections::HashMap, fmt::Debug}; +use std::{fmt::Debug}; use color_eyre::eyre::Context; use color_eyre::Result; +use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use crate::{cache::ZorkCache, domain::target::TargetIdentifier}; @@ -18,7 +17,8 @@ use crate::{cache::ZorkCache, domain::target::TargetIdentifier}; use crate::utils; use self::{ - build::BuildModel, compiler::CompilerModel, executable::ExecutableModel, modules::ModulesModel, project::ProjectModel, target::TargetModel, tests::TestsModel + build::BuildModel, compiler::CompilerModel, modules::ModulesModel, project::ProjectModel, + target::TargetModel, }; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] @@ -27,9 +27,7 @@ pub struct ZorkModel<'a> { pub compiler: CompilerModel<'a>, pub build: BuildModel, pub modules: ModulesModel<'a>, - pub targets: HashMap, TargetModel<'a>>, - pub executable: ExecutableModel<'a>, - pub tests: TestsModel<'a>, + pub targets: IndexMap, TargetModel<'a>>, } /// Loads the mapped [`ZorkModel`] for a concrete [`ZorkConfigFile`] from the [`ZorkCache`] diff --git a/zork++/src/lib/project_model/sourceset.rs b/zork++/src/lib/project_model/sourceset.rs index abfbcbdb..2e0c7c43 100644 --- a/zork++/src/lib/project_model/sourceset.rs +++ b/zork++/src/lib/project_model/sourceset.rs @@ -57,7 +57,7 @@ impl GlobPattern { } } -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Clone)] pub struct SourceSet<'a> { pub sources: Vec>, } diff --git a/zork++/src/lib/project_model/target.rs b/zork++/src/lib/project_model/target.rs index 6d6faaf4..fb2d4cfc 100644 --- a/zork++/src/lib/project_model/target.rs +++ b/zork++/src/lib/project_model/target.rs @@ -1,12 +1,13 @@ +use crate::domain::commands::arguments::Argument; use crate::domain::target::TargetKind; use crate::project_model::sourceset::SourceSet; use serde::{Deserialize, Serialize}; use std::borrow::Cow; -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Clone)] pub struct TargetModel<'a> { pub output_name: Cow<'a, str>, pub sources: SourceSet<'a>, - pub extra_args: Vec>, + pub extra_args: Vec>, pub kind: TargetKind, } diff --git a/zork++/src/lib/project_model/tests.rs b/zork++/src/lib/project_model/tests.rs deleted file mode 100644 index 7b498f1a..00000000 --- a/zork++/src/lib/project_model/tests.rs +++ /dev/null @@ -1,29 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::domain::commands::arguments::Argument; -use crate::domain::target::{ExecutableTarget, ExtraArgs}; -use std::borrow::Cow; - -use super::sourceset::SourceSet; - -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct TestsModel<'a> { - pub test_executable_name: Cow<'a, str>, - pub sourceset: SourceSet<'a>, - pub extra_args: Vec>, -} - -impl<'a> ExtraArgs<'a> for TestsModel<'a> { - fn extra_args(&'a self) -> &'a [Argument] { - &self.extra_args - } -} - -impl<'a> ExecutableTarget<'a> for TestsModel<'a> { - fn name(&'a self) -> &'a str { - &self.test_executable_name - } - fn sourceset(&'a self) -> &'a SourceSet { - &self.sourceset - } -} diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index 01069876..73398833 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -57,6 +57,8 @@ pub mod error_messages { "An error happened while generating the commands for the module implementations"; pub const FAILURE_TARGET_SOURCES: &str = "An error happened while generating the commands for the declared sources of the target"; + pub const FAILURE_FINDING_TARGET: &str = + "An error happened while retrieving the target information"; pub const FAILURE_SYSTEM_MODULES: &str = "An error happened while generating the commands for the declared system headers as modules"; pub const WRONG_DOWNCAST_FOR: &str = "An error happened while resolving the original type of"; @@ -111,21 +113,15 @@ extra_args = [ "-Wall" ] [build] output_dir = "" -[executable] -executable_name = "zork" -sources_base_path = "bin" -sources = [ - "*.cpp" -] -extra_args = [ "-Werr" ] +[targets.executable] +output_name = 'zork' +sources = [ 'main.cpp' ] +extra_args = [ '-Werr' ] -[tests] -test_executable_name = "zork_check" -sources_base_path = "test" -sources = [ - "*.cpp" -] -extra_args = [ "-pedantic" ] +[targets.tests] +output_name = 'zork_tests' +sources = [ 'tests_main.cpp' ] +target_kind = 'executable' [modules] base_ifcs_dir = "ifcs" @@ -139,6 +135,8 @@ implementations = [ { file = "maths.cpp" }, { file = 'some_module_impl.cpp', dependencies = ['iostream'] } ] + sys_modules = [ "iostream" ] + extra_args = [ "-Wall" ] "#; diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 733b3851..ceaf07dc 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -10,30 +10,26 @@ use crate::{ config_file::{ build::BuildAttribute, compiler::CompilerAttribute, - executable::ExecutableAttribute, modules::{ModuleImplementation, ModuleInterface, ModulesAttribute}, project::ProjectAttribute, - tests::TestsAttribute, ZorkConfigFile, }, project_model::{ build::BuildModel, compiler::CompilerModel, - executable::ExecutableModel, modules::{ ModuleImplementationModel, ModuleInterfaceModel, ModulePartitionModel, ModulesModel, }, project::ProjectModel, sourceset::{GlobPattern, Source, SourceSet}, - tests::TestsModel, ZorkModel, }, utils, }; use chrono::{DateTime, Utc}; use color_eyre::{eyre::eyre, Result}; +use indexmap::IndexMap; use std::borrow::Cow; -use std::collections::HashMap; use std::path::{Path, PathBuf}; use walkdir::WalkDir; @@ -109,18 +105,7 @@ pub fn build_model<'a>( let build = assemble_build_model(config.build, absolute_project_root); let modules = assemble_modules_model(config.modules, absolute_project_root); - let targets = assemble_targets_model(config.targets, absolute_project_root); - - let executable = assemble_executable_model( - Cow::Borrowed(proj_name), - config.executable, - absolute_project_root, - ); - let tests = assemble_tests_model( - Cow::Borrowed(proj_name), - config.tests, - absolute_project_root, - ); + let targets = assemble_targets_model(config.targets, proj_name, absolute_project_root); Ok(ZorkModel { project, @@ -128,12 +113,9 @@ pub fn build_model<'a>( build, modules, targets, - executable, - tests, }) } - fn assemble_project_model(config: ProjectAttribute) -> ProjectModel { ProjectModel { name: Cow::Borrowed(config.name), @@ -319,82 +301,52 @@ fn assemble_module_implementation_model<'a>( } } - -fn assemble_targets_model<'a>(targets: HashMap<&str, TargetAttribute<'a>>, absolute_project_root: &Path) -> HashMap, TargetModel<'a>> { - todo!() +fn assemble_targets_model<'a>( + targets: IndexMap<&'a str, TargetAttribute<'a>>, + project_name: &'a str, + absolute_project_root: &Path, +) -> IndexMap, TargetModel<'a>> { + targets + .into_iter() + .map(|(k, v)| { + ( + TargetIdentifier(Cow::Borrowed(k)), + assemble_target_model(v, project_name, absolute_project_root), + ) + }) + .collect() } -//noinspection ALL -fn assemble_executable_model<'a>( - project_name: Cow<'a, str>, - config: Option>, - project_root: &Path, -) -> ExecutableModel<'a> { - let config = config.as_ref(); - - let executable_name = config - .and_then(|exe| exe.executable_name) +fn assemble_target_model<'a>( + target_config: TargetAttribute<'a>, + project_name: &'a str, + absolute_project_root: &Path, +) -> TargetModel<'a> { + let sources = target_config + .sources + .into_iter() .map(Cow::Borrowed) - .unwrap_or(project_name); - - let sources = config - .and_then(|exe| exe.sources.as_ref()) - .map(|srcs| { - srcs.iter() - .map(|src| Cow::Borrowed(*src)) - .collect::>>() - }) - .unwrap_or_default(); - - let sourceset = get_sourceset_for(sources, project_root); - - let extra_args = config - .and_then(|exe| exe.extra_args.as_ref()) - .map(|args| args.iter().map(|arg| Argument::from(*arg)).collect()) - .unwrap_or_default(); + .collect(); - ExecutableModel { - executable_name, - sourceset, - extra_args, - } -} + let sources = get_sources_for_target(sources, absolute_project_root); -fn assemble_tests_model<'a>( - project_name: Cow<'_, str>, - config: Option>, - project_root: &Path, -) -> TestsModel<'a> { - let config = config.as_ref(); - - let test_executable_name = config.and_then(|exe| exe.test_executable_name).map_or_else( - || format!("{project_name}_test"), - |exe_name| exe_name.to_owned(), - ); - - let sources = config - .and_then(|exe| exe.sources.as_ref()) - .map(|srcs| { - srcs.iter() - .map(|src| Cow::Borrowed(*src)) - .collect::>>() - }) - .unwrap_or_default(); - let sourceset = get_sourceset_for(sources, project_root); - - let extra_args = config - .and_then(|test| test.extra_args.as_ref()) + let extra_args = target_config + .extra_args .map(|args| args.iter().map(|arg| Argument::from(*arg)).collect()) .unwrap_or_default(); - TestsModel { - test_executable_name: Cow::Owned(test_executable_name), - sourceset, + TargetModel { + output_name: Cow::Borrowed(target_config.output_name.unwrap_or(project_name)), + sources, extra_args, + kind: target_config.kind.unwrap_or_default(), } } -fn get_sourceset_for<'a>(srcs: Vec>, project_root: &Path) -> SourceSet<'a> { +/// Utilery function to map all the source files declared on the [`ZorkConfigFile::targets`] +/// attribute to the domain model entity, including resolving any [`GlobPattern`] declared as +/// any file on the input collection +fn get_sources_for_target<'a>(srcs: Vec>, project_root: &Path) -> SourceSet<'a> { let sources = srcs .iter() .map(|src| { @@ -430,6 +382,7 @@ mod test { use std::borrow::Cow; use crate::config_file; + use crate::domain::target::TargetKind; use crate::utils::fs; use crate::{ project_model::compiler::{CppCompiler, LanguageLevel, StdLib}, @@ -440,71 +393,44 @@ mod test { use super::*; #[test] - fn test_project_model_with_minimal_config() -> Result<()> { - const CONFIG_FILE_MOCK: &str = r#" - [project] - name = 'Zork++' - authors = ['zerodaycode.gz@gmail.com'] - - [compiler] - cpp_compiler = 'clang' - cpp_standard = '20' - "#; - - let config: ZorkConfigFile = config_file::zork_cfg_from_file(CONFIG_FILE_MOCK)?; + fn test_project_model_with_full_config() -> Result<()> { + let config: ZorkConfigFile = + config_file::zork_cfg_from_file(utils::constants::CONFIG_FILE_MOCK)?; let cli_args = CliArgs::parse_from(["", "-vv", "run"]); let abs_path_for_mock = fs::get_project_root_absolute_path(Path::new("."))?; let model = build_model(config, &cli_args, &abs_path_for_mock); - let expected = ZorkModel { - project: ProjectModel { - name: "Zork++".into(), - authors: vec!["zerodaycode.gz@gmail.com".into()], - compilation_db: false, - project_root: None, - }, - compiler: CompilerModel { - cpp_compiler: CppCompiler::CLANG, - driver_path: Cow::Borrowed(""), - cpp_standard: LanguageLevel::CPP20, - std_lib: None, - extra_args: vec![], - }, - build: BuildModel { - output_dir: abs_path_for_mock.join("out"), - }, - modules: ModulesModel { - base_ifcs_dir: Cow::default(), - interfaces: vec![], - base_impls_dir: Cow::default(), - implementations: vec![], - sys_modules: vec![], - }, - targets: Default::default(), // TODO: - executable: ExecutableModel { - executable_name: "Zork++".into(), - sourceset: SourceSet { sources: vec![] }, - extra_args: vec![], + let mut targets = IndexMap::new(); + targets.insert( + TargetIdentifier::from("executable"), + TargetModel { + output_name: "zork".into(), + sources: SourceSet { + sources: vec![SourceFile { + path: PathBuf::default(), + file_stem: Cow::Borrowed("main"), + extension: Cow::Borrowed("cpp"), + }], + }, + extra_args: vec!["-Werr".into()], + kind: TargetKind::Executable, }, - tests: TestsModel { - test_executable_name: "Zork++_test".into(), - sourceset: SourceSet { sources: vec![] }, + ); + targets.insert( + TargetIdentifier::from("tests"), + TargetModel { + output_name: "zork_tests".into(), + sources: SourceSet { + sources: vec![SourceFile { + path: PathBuf::default(), + file_stem: Cow::Borrowed("tests_main"), + extension: Cow::Borrowed("cpp"), + }], + }, extra_args: vec![], + kind: TargetKind::Executable, }, - }; - - assert_eq!(model.unwrap(), expected); - - Ok(()) - } - - #[test] - fn test_project_model_with_full_config() -> Result<()> { - let config: ZorkConfigFile = - config_file::zork_cfg_from_file(utils::constants::CONFIG_FILE_MOCK)?; - let cli_args = CliArgs::parse_from(["", "-vv", "run"]); - let abs_path_for_mock = fs::get_project_root_absolute_path(Path::new("."))?; - let model = build_model(config, &cli_args, &abs_path_for_mock); + ); let expected = ZorkModel { project: ProjectModel { @@ -563,17 +489,7 @@ mod test { ..Default::default() }], }, - executable: ExecutableModel { - executable_name: "zork".into(), - sourceset: SourceSet { sources: vec![] }, - extra_args: vec![Argument::from("-Werr")], - }, - targets: Default::default(), // TODO: - tests: TestsModel { - test_executable_name: "zork_check".into(), - sourceset: SourceSet { sources: vec![] }, - extra_args: vec![Argument::from("-pedantic")], - }, + targets, }; assert_eq!(model.unwrap(), expected); From cb03f0bd69ffa993e40f0f6ea760d5eb0d3f524d Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 24 Jul 2024 23:12:56 +0200 Subject: [PATCH 51/73] feat(wip): indepent targets now compiles and runs successfuly chore: refactored the .collect() calls on the chained iterators to just use an opaque type behind the Iterator type and avoid to consume the view of the command line arguments --- zork++/.gitignore | 1 + zork++/src/lib/cli/output/executors.rs | 160 ++++++---- zork++/src/lib/compiler/mod.rs | 19 +- zork++/src/lib/domain/commands/arguments.rs | 4 +- .../src/lib/domain/commands/command_lines.rs | 11 +- zork++/src/lib/domain/target.rs | 4 +- zork++/src/lib/lib.rs | 274 ++++++++++++++---- zork++/src/lib/project_model/mod.rs | 2 +- 8 files changed, 346 insertions(+), 129 deletions(-) create mode 100644 zork++/.gitignore diff --git a/zork++/.gitignore b/zork++/.gitignore new file mode 100644 index 00000000..d7af68e7 --- /dev/null +++ b/zork++/.gitignore @@ -0,0 +1 @@ +merging_toml/ diff --git a/zork++/src/lib/cli/output/executors.rs b/zork++/src/lib/cli/output/executors.rs index ecbfee21..400c3b1f 100644 --- a/zork++/src/lib/cli/output/executors.rs +++ b/zork++/src/lib/cli/output/executors.rs @@ -8,27 +8,20 @@ use crate::cache::EnvVars; use crate::domain::commands::arguments::{Argument, Arguments}; use crate::domain::commands::command_lines::ModulesCommands; use crate::domain::target::{Target, TargetIdentifier}; -use crate::domain::translation_unit::TranslationUnitStatus; -use crate::utils::constants::error_messages; use crate::{ - cache::ZorkCache, project_model::{compiler::CppCompiler, ZorkModel}, utils::constants, }; -use color_eyre::eyre::ContextCompat; -use color_eyre::{ - eyre::{eyre, Context}, - Report, Result, -}; +use color_eyre::{eyre::Context, Report, Result}; use indexmap::IndexMap; -pub fn run_modules_generated_commands<'a> ( - program_data: &ZorkModel<'a>, - general_args: &Arguments<'a>, +pub fn run_modules_generated_commands( + program_data: &ZorkModel<'_>, + general_args: &Arguments<'_>, compiler_specific_shared_args: &Arguments, - modules_generated_commands: &mut ModulesCommands<'a>, + modules_generated_commands: &mut ModulesCommands<'_>, env_vars: &EnvVars, -)-> Result<()> { +) -> Result<()> { log::info!("Proceeding to execute the generated modules commands..."); // Process the modules @@ -41,11 +34,12 @@ pub fn run_modules_generated_commands<'a> ( ) } -pub fn run_targets_generated_commands<'a>( - program_data: &ZorkModel<'a>, - general_args: &Arguments<'a>, +pub fn run_targets_generated_commands( + program_data: &ZorkModel<'_>, + general_args: &Arguments<'_>, compiler_specific_shared_args: &Arguments, targets: &mut IndexMap, + modules: &ModulesCommands<'_>, env_vars: &EnvVars, ) -> Result<()> { log::info!("Proceeding to execute the generated commands..."); @@ -57,23 +51,27 @@ pub fn run_targets_generated_commands<'a>( "Executing the linker command line for target: {:?}", target_name ); - let r = helpers::execute_linker_command_line( + + // Send to build to the compiler the sources declared for the current iteration target + for source in target_data.sources.iter_mut() { + helpers::execute_source_command_line( + program_data, + general_args, + compiler_specific_shared_args, + env_vars, + source, + )?; + } + + // Invoke the linker to generate the final product for the current iteration target + helpers::execute_linker_command_line( program_data, general_args, compiler_specific_shared_args, - &target_data.linker, + modules, env_vars, - target_data - ); - target_data.linker.execution_result = TranslationUnitStatus::from(&r); - - if let Err(e) = r { - return Err(e); - } else if !r.as_ref().unwrap().success() { - return Err(eyre!( - "Ending the program, because the linker command line execution failed", - )); - } + target_data, + )?; } Ok(()) @@ -89,7 +87,6 @@ pub fn autorun_generated_binary( let args = &[Argument::from( output_dir .join(compiler.as_ref()) - // TODO: join with the correct value .join(executable_name) .with_extension(constants::BINARY_EXTENSION), )]; @@ -111,75 +108,129 @@ pub fn autorun_generated_binary( /// Executes a new [`std::process::Command`] configured according the chosen /// compiler and the current operating system -fn execute_command( +fn execute_command<'a, T, S>( model: &ZorkModel, - arguments: T, + arguments: &mut T, env_vars: &EnvVars, ) -> Result where - T: IntoIterator + std::fmt::Display + Copy, + // T: IntoIterator + std::fmt::Display + Copy, + T: Iterator + std::fmt::Debug, S: AsRef, + Arguments<'a>: FromIterator, { let compiler = model.compiler.cpp_compiler; + let driver = compiler.get_driver(&model.compiler); log::trace!( "[{compiler}] - Executing command => {:?}", - format!("{} {arguments}", compiler.get_driver(&model.compiler),) + format!("{} {}", driver, arguments.collect::()) ); - let driver = compiler.get_driver(&model.compiler); let os_driver = OsStr::new(driver.as_ref()); std::process::Command::new(os_driver) .args(arguments) .envs(env_vars) .spawn()? .wait() - .with_context(|| format!("[{compiler}] - Command {arguments} failed!")) + .with_context(|| format!("[{compiler}] - Command failed!")) } mod helpers { use crate::cache::EnvVars; use crate::cli::output::executors::execute_command; use crate::domain::commands::arguments::{Argument, Arguments}; - use crate::domain::commands::command_lines::{ - LinkerCommandLine, ModulesCommands, SourceCommandLine, - }; + use crate::domain::commands::command_lines::{ModulesCommands, SourceCommandLine}; use crate::domain::target::Target; use crate::domain::translation_unit::TranslationUnitStatus; use crate::project_model::compiler::CppCompiler; use crate::project_model::ZorkModel; use color_eyre::eyre::{eyre, Result}; + use std::collections::HashMap; use std::process::ExitStatus; + pub(crate) fn execute_source_command_line( + program_data: &ZorkModel<'_>, + general_args: &Arguments<'_>, + compiler_specific_shared_args: &Arguments<'_>, + env_vars: &HashMap, + source: &mut SourceCommandLine<'_>, + ) -> Result<()> { + let compile_but_dont_link = [Argument::from("/c")]; + let mut args = general_args + .iter() + .chain(compiler_specific_shared_args.iter()) + .chain(source.args.as_slice().iter()) + .chain(compile_but_dont_link.iter()); + + let r = execute_command(program_data, &mut args, env_vars); + source.status = TranslationUnitStatus::from(&r); + + if let Err(e) = r { + return Err(e); + } else if !r.as_ref().unwrap().success() { + let err = eyre!( + "Ending the program, because the build of: {:?} failed", + source.filename + ); + return Err(err); + } + + Ok(()) + } + pub(crate) fn execute_linker_command_line( program_data: &ZorkModel, general_args: &Arguments, compiler_specific_shared_args: &Arguments, - linker_command_line: &LinkerCommandLine, + modules: &ModulesCommands<'_>, env_vars: &EnvVars, - target_data: &Target + target_data: &mut Target, ) -> Result { - let linker_args = - linker_command_line.get_target_output_for(program_data.compiler.cpp_compiler); + let linker_args = target_data + .linker + .get_target_output_for(program_data.compiler.cpp_compiler); let linker_sources_byproducts = target_data.sources.iter().map(|scl| &scl.byproduct); - // let modules_sources_byproducts = + let modules_byproducts = modules + .cpp_stdlib + .as_slice() + .iter() + .chain(modules.c_compat_stdlib.iter()) + .chain(modules.interfaces.iter()) + .chain(modules.implementations.iter()) + .chain(modules.system_modules.iter()) + .map(|scl| &scl.byproduct); - let args = general_args + let mut args = general_args .iter() .chain(linker_args.iter()) .chain(compiler_specific_shared_args.iter()) - .chain(linker_command_line.byproducts.iter()) + // TODO: // .chain(linker_command_line.byproducts.iter()) + // TODO: // .chain(modules.byproducts.iter()) review if it's worth to have them on + // separated caches entries or build the chained iterators .chain(linker_sources_byproducts) - .collect::(); - execute_command(program_data, &args, env_vars) + .chain(modules_byproducts); + + let r = execute_command(program_data, &mut args, env_vars); + target_data.linker.execution_result = TranslationUnitStatus::from(&r); + + if let Err(e) = r { + return Err(e); + } else if !r.as_ref().unwrap().success() { + return Err(eyre!( + "Ending the program, because the linker command line execution failed", + )); + } + + r } - pub(crate) fn process_modules_commands<'a>( - program_data: &ZorkModel<'a>, + pub(crate) fn process_modules_commands( + program_data: &ZorkModel<'_>, general_args: &Arguments, compiler_specific_shared_args: &Arguments, - generated_commands: &mut ModulesCommands<'a>, + generated_commands: &mut ModulesCommands<'_>, env_vars: &std::collections::HashMap, ) -> Result<()> { let translation_units_commands: Vec<&mut SourceCommandLine> = @@ -205,14 +256,13 @@ mod helpers { for translation_unit_cmd in translation_units_commands { // Join the concrete args of any translation unit with the ones held in the flyweights - let translation_unit_cmd_args: Arguments = general_args + let mut translation_unit_cmd_args = general_args .iter() .chain(compiler_specific_shared_args.iter()) .chain(&compile_but_dont_link) - .chain(translation_unit_cmd.args.iter()) - .collect(); + .chain(translation_unit_cmd.args.iter()); - let r = execute_command(program_data, &translation_unit_cmd_args, env_vars); + let r = execute_command(program_data, &mut translation_unit_cmd_args, env_vars); translation_unit_cmd.status = TranslationUnitStatus::from(&r); if let Err(e) = r { diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index b5788241..4f1c51a0 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -174,7 +174,8 @@ fn generate_sources_cmds_args<'a>( model, cache, cli_args, - &target_data.sources.sources, + &target_data.sources.sources, // FIX: the sources.sources access with the repeated field + // names TranslationUnitKind::SourceFile(target_identifier), ) .with_context(|| error_messages::FAILURE_TARGET_SOURCES) @@ -211,7 +212,7 @@ pub fn generate_linker_general_command_line_args<'a>( .generated_commands .targets .get_mut(target_identifier) - .unwrap() + .unwrap() // TODO: care, better use with_context, even tho this is a non-failable unwrap .linker; let compiler = &model.compiler.cpp_compiler; @@ -220,10 +221,11 @@ pub fn generate_linker_general_command_line_args<'a>( let target_output = Argument::from( out_dir .join(compiler.as_ref()) - .join(target_identifier.value().as_ref()) + .join(target_identifier.name()) .with_extension(constants::BINARY_EXTENSION), ); + // Check if its necessary to change the target output details if linker.target.ne(&target_output) { match compiler { CppCompiler::CLANG | CppCompiler::GCC => linker.target = target_output, @@ -231,6 +233,8 @@ pub fn generate_linker_general_command_line_args<'a>( }; } + // Check if the extra args passed by the user to the linker has changed from previous + // iterations if Iterator::ne(linker.extra_args.iter(), target_details.extra_args.iter()) { linker.extra_args.clear(); linker @@ -263,7 +267,8 @@ fn process_kind_translation_units<'a, T: TranslationUnit<'a>>( fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, - _cli_args: &'a CliArgs, + _cli_args: &'a CliArgs, // TODO: review if it will be further required on other evolutions of + // the codebase translation_unit: &'a T, for_kind: &TranslationUnitKind<'a>, ) -> Result<()> { @@ -302,7 +307,7 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( let resolved_tu = transient::Downcast::downcast_ref::(tu_with_erased_type) .with_context(|| helpers::wrong_downcast_msg(translation_unit))?; - sources::generate_sources_arguments(model, cache, resolved_tu, &related_target)?; + sources::generate_sources_arguments(model, cache, resolved_tu, related_target)?; } TranslationUnitKind::SystemHeader => { let resolved_tu = @@ -319,7 +324,7 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( /// Command line arguments generators procedures for C++ standard modules mod modules { - use std::path::{Path}; + use std::path::Path; use crate::cache::ZorkCache; use crate::compiler::helpers; @@ -519,7 +524,7 @@ mod sources { use crate::cache::ZorkCache; use crate::domain::commands::arguments::Arguments; use crate::domain::commands::command_lines::SourceCommandLine; - use crate::domain::target::{TargetIdentifier}; + use crate::domain::target::TargetIdentifier; use crate::domain::translation_unit::TranslationUnit; use crate::project_model::sourceset::SourceFile; use crate::project_model::target::TargetModel; diff --git a/zork++/src/lib/domain/commands/arguments.rs b/zork++/src/lib/domain/commands/arguments.rs index 25eef6ae..0ebf0686 100644 --- a/zork++/src/lib/domain/commands/arguments.rs +++ b/zork++/src/lib/domain/commands/arguments.rs @@ -149,7 +149,7 @@ impl<'a> Arguments<'a> { } /// Extends the underlying collection given a slice of any type that is convertible to [`Argument`] - /// and clonable + /// and implements [`Clone`] pub fn extend_from_to_argument_slice(&mut self, slice: &'a [F]) where F: Into> + Clone, @@ -157,7 +157,7 @@ impl<'a> Arguments<'a> { self.0.extend( slice .iter() - .map(|arg| Argument::from(>::into(arg.clone()))), + .map(|arg| >::into(arg.clone())), ); } diff --git a/zork++/src/lib/domain/commands/command_lines.rs b/zork++/src/lib/domain/commands/command_lines.rs index 66972834..18643c33 100644 --- a/zork++/src/lib/domain/commands/command_lines.rs +++ b/zork++/src/lib/domain/commands/command_lines.rs @@ -7,7 +7,6 @@ use crate::utils::constants::error_messages; use color_eyre::eyre::{ContextCompat, Result}; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::fmt::Debug; use std::path::{Path, PathBuf}; @@ -20,6 +19,8 @@ use std::path::{Path, PathBuf}; /// * status*: A [`TranslationUnitStatus`] that represents all the different phases that a source command /// line can have among all the different iterations of the program, changing according to the modifications /// over the translation unit in the fs and the result of the build execution +/// *byproduct*: A [`PathBuf`] like [`Argument`] which hold the physical address on the filesystem +/// where the compiled object file will be dumped after building it #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct SourceCommandLine<'a> { pub directory: PathBuf, @@ -30,7 +31,11 @@ pub struct SourceCommandLine<'a> { } impl<'a> SourceCommandLine<'a> { - pub fn new, B: Into>>(tu: &T, args: Arguments<'a>, byproduct: B) -> Self { + pub fn new, B: Into>>( + tu: &T, + args: Arguments<'a>, + byproduct: B, + ) -> Self { Self { directory: PathBuf::from(tu.parent()), filename: tu.filename(), @@ -51,6 +56,8 @@ impl<'a> SourceCommandLine<'a> { #[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)] pub struct LinkerCommandLine<'a> { + // TODO: review if we do need this struct yet, since target does + // the same link_modules: bool, // TODO: pending pub target: Argument<'a>, pub modules_byproducts: Arguments<'a>, diff --git a/zork++/src/lib/domain/target.rs b/zork++/src/lib/domain/target.rs index af8b0fde..ae12a62c 100644 --- a/zork++/src/lib/domain/target.rs +++ b/zork++/src/lib/domain/target.rs @@ -42,8 +42,8 @@ impl<'a> From<&'a str> for TargetIdentifier<'a> { } impl<'a> TargetIdentifier<'a> { - pub fn value(&self) -> &Cow<'a, str> { - &self.0 + pub fn name(&'a self) -> &'a str { + self.0.as_ref() } } diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 50f0d2e8..ae3a5f9e 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -41,6 +41,148 @@ pub mod worker { /// The main work of the project. Runs the tasks /// inputted in the CLI + /* pub fn run_zork(cli_args: &CliArgs) -> std::result::Result<(), Report> { + let project_root = cli_args + .root + .as_deref() + .map(Path::new) + .unwrap_or(Path::new(".")); + let abs_project_root = get_project_root_absolute_path(project_root)?; + // TODO: falta o do project model, gañans! + if let Command::New { + ref name, + git, + compiler, + template, + } = cli_args.command + { + // TODO: pass here the driver's path? so it's already configured on the autogenerated + // zork.toml file? + return create_templated_project( + &abs_project_root, + name, + git, + compiler.into(), + template, + ); + }; + + let config_files: Vec = find_config_files(project_root, &cli_args.match_files)?; + + for config_file in config_files { + let cfg_path = &config_file.path; + log::debug!( + "Launching a Zork++ work event for the configuration file: {:?}", + cfg_path, + ); + let raw_file = fs::read_to_string(cfg_path) + .with_context(|| format!("{}: {:?}", error_messages::READ_CFG_FILE, cfg_path))?; + + let config: ZorkConfigFile<'_> = config_file::zork_cfg_from_file(raw_file.as_str()) + .with_context(|| error_messages::PARSE_CFG_FILE)?; + + create_output_directory(&config, &abs_project_root)?; + + let mut cache: ZorkCache<'_> = cache::load(&config, cli_args, &abs_project_root)?; + + let program_data: ZorkModel<'_> = + if config_file.last_time_modified > cache.metadata.last_program_execution { + cache.metadata.save_project_model = true; + utils::reader::build_model(config, cli_args, &abs_project_root)? + } else { + log::debug!("Loading the ZorkModel from the cache"); + project_model::load(&cache)? + }; + + map_model_targets_to_cache(&program_data, &mut cache); + + let generate_commands_ts = Instant::now(); + // Generates the commands for every translation unit and/or checks on successive iterations + // of the program it any of them has been modified so the files must be marked to be + // rebuilt again + generate_commands(&program_data, &mut cache, cli_args) + .with_context(|| error_messages::FAILURE_GENERATING_COMMANDS)?; + + log::debug!( + "Zork++ took a total of {:?} ms on handling the generated commands", + generate_commands_ts.elapsed().as_millis() + ); + {let tmp = do_main_work_based_on_cli_input(cli_args, &program_data, &mut cache).with_context(|| { + format!( + "{}: {:?}", + error_messages::FAILED_BUILD_FOR_CFG_FILE, + cfg_path + ) + })?; + } + + cache.save(&program_data, cli_args)? + + } + + + + Ok(()) + } + + /// Helper for reduce the cyclomatic complexity of the main fn. + /// + /// Contains the main calls to the generation of the compilers commands lines, + /// the calls to the process that runs those, the autorun the generated + /// binaries, the tests declared for the projects... + fn do_main_work_based_on_cli_input<'a, 'b: 'a>( + cli_args: &'b CliArgs, + program_data: &'b ZorkModel<'b>, + cache: &'b mut ZorkCache<'b>, + ) -> Result<()> { + + let generated_commands = &mut cache.generated_commands; + + let general_args = generated_commands + .general_args + .as_mut() + .with_context(|| error_messages::GENERAL_ARGS_NOT_FOUND)? + .get_args(); + + let compiler_specific_shared_args = generated_commands + .compiler_common_args + .as_mut() + .with_context(|| error_messages::COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND)? + .get_args(); + + let env_vars = match program_data.compiler.cpp_compiler { + CppCompiler::MSVC => &cache.compilers_metadata.msvc.env_vars, + CppCompiler::CLANG => &cache.compilers_metadata.clang.env_vars, + CppCompiler::GCC => &cache.compilers_metadata.gcc.env_vars, + }; + + executors::run_modules_generated_commands(program_data, &general_args, &compiler_specific_shared_args, &mut generated_commands.modules, &env_vars)?; + // TODO: cache save by not propagating Err with ? + + match cli_args.command { + Command::Build => executors::run_targets_generated_commands(program_data, &general_args, &compiler_specific_shared_args, &mut generated_commands.targets, &env_vars), + Command::Run | Command::Test => { + // TODO: for target in cache and filtered by cmdargs + match executors::run_targets_generated_commands(program_data, &general_args, &compiler_specific_shared_args, &mut generated_commands.targets, &env_vars) { + Ok(_) => { + for (target_name, target_data) in generated_commands.targets.iter() { + executors::autorun_generated_binary( + &program_data.compiler.cpp_compiler, + &program_data.build.output_dir, + target_name.value(), + // &program_data.executable.executable_name, + )? + } + return Ok(()); + } + Err(e) => Err(e), + } + }, + _ => todo!("{}", error_messages::CLI_ARGS_CMD_NEW_BRANCH), + } + } + */ + pub fn run_zork(cli_args: &CliArgs) -> std::result::Result<(), Report> { let project_root = cli_args .root @@ -48,7 +190,7 @@ pub mod worker { .map(Path::new) .unwrap_or(Path::new(".")); let abs_project_root = get_project_root_absolute_path(project_root)?; - // TODO: falta o do project model, gañans! + if let Command::New { ref name, git, @@ -56,8 +198,6 @@ pub mod worker { template, } = cli_args.command { - // TODO: pass here the driver's path? so it's already configured on the autogenerated - // zork.toml file? return create_templated_project( &abs_project_root, name, @@ -96,49 +236,53 @@ pub mod worker { map_model_targets_to_cache(&program_data, &mut cache); - let generate_commands_ts = Instant::now(); - // Generates the commands for every translation unit and/or checks on successive iterations - // of the program it any of them has been modified so the files must be marked to be - // rebuilt again - generate_commands(&program_data, &mut cache, cli_args) - .with_context(|| error_messages::FAILURE_GENERATING_COMMANDS)?; - - log::debug!( - "Zork++ took a total of {:?} ms on handling the generated commands", - generate_commands_ts.elapsed().as_millis() - ); - let tmp = do_main_work_based_on_cli_input(cli_args, &program_data, cache).with_context(|| { - format!( - "{}: {:?}", - error_messages::FAILED_BUILD_FOR_CFG_FILE, - cfg_path - ) - })?; - + let generate_commands_ts = Instant::now(); + generate_commands(&program_data, &mut cache, cli_args) + .with_context(|| error_messages::FAILURE_GENERATING_COMMANDS)?; - } + log::debug!( + "Zork++ took a total of {:?} ms on handling the generated commands", + generate_commands_ts.elapsed().as_millis() + ); + // Perform main work + perform_main_work(cli_args, &program_data, &mut cache, cfg_path)?; + // Now save the cache + cache.save(&program_data, cli_args)?; + } Ok(()) } - /// Helper for reduce the cyclomatic complexity of the main fn. - /// - /// Contains the main calls to the generation of the compilers commands lines, - /// the calls to the process that runs those, the autorun the generated - /// binaries, the tests declared for the projects... - fn do_main_work_based_on_cli_input<'a>( - cli_args: &'a CliArgs, - program_data: &'a ZorkModel<'a>, - mut cache: ZorkCache<'a>, + fn perform_main_work( + cli_args: &CliArgs, + program_data: &ZorkModel<'_>, + cache: &mut ZorkCache<'_>, + cfg_path: &Path, ) -> Result<()> { + do_main_work_based_on_cli_input(cli_args, program_data, cache).with_context(|| { + format!( + "{}: {:?}", + error_messages::FAILED_BUILD_FOR_CFG_FILE, + cfg_path + ) + }) + } + fn do_main_work_based_on_cli_input( + cli_args: &CliArgs, + program_data: &ZorkModel<'_>, + cache: &mut ZorkCache<'_>, + ) -> Result<()> { let generated_commands = &mut cache.generated_commands; let general_args = generated_commands - .general_args.clone() - .with_context(|| error_messages::GENERAL_ARGS_NOT_FOUND)? + .general_args + .as_mut() + .with_context(|| error_messages::GENERAL_ARGS_NOT_FOUND)? // TODO: remove the optionals + // from the shared data on + // the flyweights .get_args(); let compiler_specific_shared_args = generated_commands @@ -153,43 +297,53 @@ pub mod worker { CppCompiler::GCC => &cache.compilers_metadata.gcc.env_vars, }; - executors::run_modules_generated_commands(program_data, &general_args, &compiler_specific_shared_args, &mut generated_commands.modules, &env_vars)?; - // TODO: cache save by not propagating Err with ? + executors::run_modules_generated_commands( + program_data, + &general_args, + &compiler_specific_shared_args, + &mut generated_commands.modules, + env_vars, + )?; match cli_args.command { - Command::Build => executors::run_targets_generated_commands(program_data, &general_args, &compiler_specific_shared_args, &mut generated_commands.targets, &env_vars), - Command::Run | Command::Test => { - // TODO: for target in cache and filtered by cmdargs - match executors::run_targets_generated_commands(program_data, &general_args, &compiler_specific_shared_args, &mut generated_commands.targets, &env_vars) { - Ok(_) => { - for (target_name, target_data) in generated_commands.targets.iter() { - executors::autorun_generated_binary( - &program_data.compiler.cpp_compiler, - &program_data.build.output_dir, - target_name.value(), - // &program_data.executable.executable_name, - )? - } - return Ok(()); + Command::Build => executors::run_targets_generated_commands( + program_data, + &general_args, + &compiler_specific_shared_args, + &mut generated_commands.targets, + &generated_commands.modules, + env_vars, + ), // TODO: group the duplicated calls + Command::Run | Command::Test => match executors::run_targets_generated_commands( + program_data, + &general_args, + &compiler_specific_shared_args, + &mut generated_commands.targets, + &generated_commands.modules, + env_vars, + ) { + Ok(_) => { + for target_name in generated_commands.targets.keys() { + executors::autorun_generated_binary( + &program_data.compiler.cpp_compiler, + &program_data.build.output_dir, + target_name.name(), + )? } - Err(e) => Err(e), + + return Ok(()); } - } + Err(e) => Err(e), + }?, _ => todo!("{}", error_messages::CLI_ARGS_CMD_NEW_BRANCH), - }; - - - cache.save(&program_data, cli_args) + } } /// Helper to map the user declared targets on the [`ZorkModel`], previously mapped from the /// [`ZorkConfigFile`] into the [`ZorkCache`], in order to avoid later calls with entry or insert /// which will be hidden on the code an harder to read for newcomers or after time without /// reading the codebase - fn map_model_targets_to_cache<'a>( - program_data: &ZorkModel<'a>, - cache: &mut ZorkCache<'a>, - ) { + fn map_model_targets_to_cache<'a>(program_data: &ZorkModel<'a>, cache: &mut ZorkCache<'a>) { for (target_identifier, target_data) in program_data.targets.iter() { if !cache .generated_commands diff --git a/zork++/src/lib/project_model/mod.rs b/zork++/src/lib/project_model/mod.rs index 655d5e0c..ef4fd25a 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -5,7 +5,7 @@ pub mod project; pub mod sourceset; pub mod target; -use std::{fmt::Debug}; +use std::fmt::Debug; use color_eyre::eyre::Context; use color_eyre::Result; From c1c85b38b0b580c15c7ef27cc1f514b3afe37529 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 25 Jul 2024 10:45:53 +0200 Subject: [PATCH 52/73] fix: rollbacked changes until the new targets model builds the commands on Zero again --- zork++/src/lib/cli/output/executors.rs | 54 +++++++++++++------------- zork++/src/lib/lib.rs | 8 +++- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/zork++/src/lib/cli/output/executors.rs b/zork++/src/lib/cli/output/executors.rs index 400c3b1f..c0169371 100644 --- a/zork++/src/lib/cli/output/executors.rs +++ b/zork++/src/lib/cli/output/executors.rs @@ -8,11 +8,15 @@ use crate::cache::EnvVars; use crate::domain::commands::arguments::{Argument, Arguments}; use crate::domain::commands::command_lines::ModulesCommands; use crate::domain::target::{Target, TargetIdentifier}; +use crate::domain::translation_unit::TranslationUnitStatus; use crate::{ project_model::{compiler::CppCompiler, ZorkModel}, utils::constants, }; -use color_eyre::{eyre::Context, Report, Result}; +use color_eyre::{ + eyre::{eyre, Context}, + Report, Result, +}; use indexmap::IndexMap; pub fn run_modules_generated_commands( @@ -64,14 +68,23 @@ pub fn run_targets_generated_commands( } // Invoke the linker to generate the final product for the current iteration target - helpers::execute_linker_command_line( + let r = helpers::execute_linker_command_line( program_data, general_args, compiler_specific_shared_args, modules, env_vars, target_data, - )?; + ); + target_data.linker.execution_result = TranslationUnitStatus::from(&r); + + if let Err(e) = r { + return Err(e); + } else if !r.as_ref().unwrap().success() { + return Err(eyre!( + "Ending the program, because the linker command line execution failed", + )); + } } Ok(()) @@ -87,6 +100,7 @@ pub fn autorun_generated_binary( let args = &[Argument::from( output_dir .join(compiler.as_ref()) + // TODO: join with the correct value .join(executable_name) .with_extension(constants::BINARY_EXTENSION), )]; @@ -108,7 +122,7 @@ pub fn autorun_generated_binary( /// Executes a new [`std::process::Command`] configured according the chosen /// compiler and the current operating system -fn execute_command<'a, T, S>( +fn execute_command( model: &ZorkModel, arguments: &mut T, env_vars: &EnvVars, @@ -117,22 +131,21 @@ where // T: IntoIterator + std::fmt::Display + Copy, T: Iterator + std::fmt::Debug, S: AsRef, - Arguments<'a>: FromIterator, { let compiler = model.compiler.cpp_compiler; - let driver = compiler.get_driver(&model.compiler); log::trace!( "[{compiler}] - Executing command => {:?}", - format!("{} {}", driver, arguments.collect::()) + format!("{} {:?}", compiler.get_driver(&model.compiler), arguments) ); + let driver = compiler.get_driver(&model.compiler); let os_driver = OsStr::new(driver.as_ref()); - std::process::Command::new(os_driver) + Ok(std::process::Command::new(os_driver) .args(arguments) .envs(env_vars) .spawn()? - .wait() - .with_context(|| format!("[{compiler}] - Command failed!")) + .wait()?) + // .with_context(|| format!("[{compiler}] - Command {:?} failed!", arguments)) } mod helpers { @@ -162,7 +175,6 @@ mod helpers { .chain(compiler_specific_shared_args.iter()) .chain(source.args.as_slice().iter()) .chain(compile_but_dont_link.iter()); - let r = execute_command(program_data, &mut args, env_vars); source.status = TranslationUnitStatus::from(&r); @@ -185,7 +197,7 @@ mod helpers { compiler_specific_shared_args: &Arguments, modules: &ModulesCommands<'_>, env_vars: &EnvVars, - target_data: &mut Target, + target_data: &Target, ) -> Result { let linker_args = target_data .linker @@ -206,24 +218,12 @@ mod helpers { .iter() .chain(linker_args.iter()) .chain(compiler_specific_shared_args.iter()) - // TODO: // .chain(linker_command_line.byproducts.iter()) - // TODO: // .chain(modules.byproducts.iter()) review if it's worth to have them on - // separated caches entries or build the chained iterators + // .chain(linker_command_line.byproducts.iter()) .chain(linker_sources_byproducts) .chain(modules_byproducts); - let r = execute_command(program_data, &mut args, env_vars); - target_data.linker.execution_result = TranslationUnitStatus::from(&r); - - if let Err(e) = r { - return Err(e); - } else if !r.as_ref().unwrap().success() { - return Err(eyre!( - "Ending the program, because the linker command line execution failed", - )); - } - - r + log::warn!("-----Linker args: {:?}", &args); + execute_command(program_data, &mut args, env_vars) } pub(crate) fn process_modules_commands( diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index ae3a5f9e..47d7a586 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -246,10 +246,16 @@ pub mod worker { ); // Perform main work - perform_main_work(cli_args, &program_data, &mut cache, cfg_path)?; + let cfg_result = perform_main_work(cli_args, &program_data, &mut cache, cfg_path); // Now save the cache cache.save(&program_data, cli_args)?; + + // Handle the errors after ensure that the cache is saved (if it didn't failed) + if cfg_result.is_err() { + log::error!("Failed to complete the job for: {:?}", cfg_path); + cfg_result? + } } Ok(()) From c3180df36a767db26fb5cddfec8bef4abaf5b9d5 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 25 Jul 2024 18:19:51 +0200 Subject: [PATCH 53/73] chore: minimal opts --- zork++/src/lib/cli/output/executors.rs | 81 ++++++++++++++------------ 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/zork++/src/lib/cli/output/executors.rs b/zork++/src/lib/cli/output/executors.rs index c0169371..278dc035 100644 --- a/zork++/src/lib/cli/output/executors.rs +++ b/zork++/src/lib/cli/output/executors.rs @@ -8,15 +8,11 @@ use crate::cache::EnvVars; use crate::domain::commands::arguments::{Argument, Arguments}; use crate::domain::commands::command_lines::ModulesCommands; use crate::domain::target::{Target, TargetIdentifier}; -use crate::domain::translation_unit::TranslationUnitStatus; use crate::{ project_model::{compiler::CppCompiler, ZorkModel}, utils::constants, }; -use color_eyre::{ - eyre::{eyre, Context}, - Report, Result, -}; +use color_eyre::{eyre::Context, Report, Result}; use indexmap::IndexMap; pub fn run_modules_generated_commands( @@ -52,10 +48,9 @@ pub fn run_targets_generated_commands( // TODO: avoid the callee of the autorun binary by decoupling modules from linkers execs for (target_name, target_data) in targets { log::info!( - "Executing the linker command line for target: {:?}", + "Executing the generated commands of the sources declared for target: {:?}", target_name ); - // Send to build to the compiler the sources declared for the current iteration target for source in target_data.sources.iter_mut() { helpers::execute_source_command_line( @@ -67,24 +62,19 @@ pub fn run_targets_generated_commands( )?; } + log::info!( + "Executing the linker command line for target: {:?}", + target_name + ); // Invoke the linker to generate the final product for the current iteration target - let r = helpers::execute_linker_command_line( + helpers::execute_linker_command_line( program_data, general_args, compiler_specific_shared_args, modules, env_vars, target_data, - ); - target_data.linker.execution_result = TranslationUnitStatus::from(&r); - - if let Err(e) = r { - return Err(e); - } else if !r.as_ref().unwrap().success() { - return Err(eyre!( - "Ending the program, because the linker command line execution failed", - )); - } + )?; } Ok(()) @@ -124,28 +114,32 @@ pub fn autorun_generated_binary( /// compiler and the current operating system fn execute_command( model: &ZorkModel, - arguments: &mut T, + arguments: T, env_vars: &EnvVars, ) -> Result where - // T: IntoIterator + std::fmt::Display + Copy, - T: Iterator + std::fmt::Debug, + T: IntoIterator + std::fmt::Display + std::marker::Copy, S: AsRef, { + /* fn execute_command( + model: &ZorkModel, + arguments: &Arguments, + env_vars: &EnvVars, + ) -> Result { */ let compiler = model.compiler.cpp_compiler; log::trace!( "[{compiler}] - Executing command => {:?}", - format!("{} {:?}", compiler.get_driver(&model.compiler), arguments) + format!("{} {}", compiler.get_driver(&model.compiler), arguments) ); let driver = compiler.get_driver(&model.compiler); let os_driver = OsStr::new(driver.as_ref()); - Ok(std::process::Command::new(os_driver) + std::process::Command::new(os_driver) .args(arguments) .envs(env_vars) .spawn()? - .wait()?) - // .with_context(|| format!("[{compiler}] - Command {:?} failed!", arguments)) + .wait() + .with_context(|| format!("[{compiler}] - Command {} failed!", arguments)) } mod helpers { @@ -170,12 +164,14 @@ mod helpers { source: &mut SourceCommandLine<'_>, ) -> Result<()> { let compile_but_dont_link = [Argument::from("/c")]; - let mut args = general_args + let args = general_args .iter() .chain(compiler_specific_shared_args.iter()) .chain(source.args.as_slice().iter()) - .chain(compile_but_dont_link.iter()); - let r = execute_command(program_data, &mut args, env_vars); + .chain(compile_but_dont_link.iter()) + .collect::(); + + let r = execute_command(program_data, &args, env_vars); source.status = TranslationUnitStatus::from(&r); if let Err(e) = r { @@ -197,7 +193,7 @@ mod helpers { compiler_specific_shared_args: &Arguments, modules: &ModulesCommands<'_>, env_vars: &EnvVars, - target_data: &Target, + target_data: &mut Target, ) -> Result { let linker_args = target_data .linker @@ -214,16 +210,28 @@ mod helpers { .chain(modules.system_modules.iter()) .map(|scl| &scl.byproduct); - let mut args = general_args + let args = general_args .iter() .chain(linker_args.iter()) .chain(compiler_specific_shared_args.iter()) // .chain(linker_command_line.byproducts.iter()) .chain(linker_sources_byproducts) - .chain(modules_byproducts); + .chain(modules_byproducts) + .collect::(); + + let r = execute_command(program_data, &args, env_vars); + + target_data.linker.execution_result = TranslationUnitStatus::from(&r); + + if let Err(e) = r { + return Err(e); + } else if !r.as_ref().unwrap().success() { + return Err(eyre!( + "Ending the program, because the linker command line execution failed", + )); + } - log::warn!("-----Linker args: {:?}", &args); - execute_command(program_data, &mut args, env_vars) + r } pub(crate) fn process_modules_commands( @@ -256,13 +264,14 @@ mod helpers { for translation_unit_cmd in translation_units_commands { // Join the concrete args of any translation unit with the ones held in the flyweights - let mut translation_unit_cmd_args = general_args + let translation_unit_cmd_args = general_args .iter() .chain(compiler_specific_shared_args.iter()) .chain(&compile_but_dont_link) - .chain(translation_unit_cmd.args.iter()); + .chain(translation_unit_cmd.args.iter()) + .collect::(); - let r = execute_command(program_data, &mut translation_unit_cmd_args, env_vars); + let r = execute_command(program_data, &translation_unit_cmd_args, env_vars); translation_unit_cmd.status = TranslationUnitStatus::from(&r); if let Err(e) = r { From e4c7a4e7902dbaf42a8914046fb0ff25c92b660e Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 25 Jul 2024 22:33:03 +0200 Subject: [PATCH 54/73] feat: filtering out the target sources that doesn't need to be rebuilt --- zork++/src/lib/cli/output/executors.rs | 27 +++++++++---- zork++/src/lib/lib.rs | 55 ++++++++++++++++---------- 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/zork++/src/lib/cli/output/executors.rs b/zork++/src/lib/cli/output/executors.rs index 278dc035..628a2993 100644 --- a/zork++/src/lib/cli/output/executors.rs +++ b/zork++/src/lib/cli/output/executors.rs @@ -2,12 +2,14 @@ //! by Zork++ use std::ffi::OsStr; +use std::time::Instant; use std::{path::Path, process::ExitStatus}; use crate::cache::EnvVars; use crate::domain::commands::arguments::{Argument, Arguments}; use crate::domain::commands::command_lines::ModulesCommands; use crate::domain::target::{Target, TargetIdentifier}; +use crate::domain::translation_unit::TranslationUnitStatus; use crate::{ project_model::{compiler::CppCompiler, ZorkModel}, utils::constants, @@ -45,14 +47,19 @@ pub fn run_targets_generated_commands( log::info!("Proceeding to execute the generated commands..."); // Process the user declared targets TODO: Filtered by cli? - // TODO: avoid the callee of the autorun binary by decoupling modules from linkers execs for (target_name, target_data) in targets { log::info!( "Executing the generated commands of the sources declared for target: {:?}", target_name ); + // Send to build to the compiler the sources declared for the current iteration target - for source in target_data.sources.iter_mut() { + let srcs_time = Instant::now(); + for source in target_data + .sources + .iter_mut() + .filter(|scl| scl.status.eq(&TranslationUnitStatus::PendingToBuild)) + { helpers::execute_source_command_line( program_data, general_args, @@ -61,7 +68,13 @@ pub fn run_targets_generated_commands( source, )?; } + log::debug!( + "Took {:?} in process the sources for target: {}", + srcs_time.elapsed(), + target_name.name() + ); + let linker_time = Instant::now(); log::info!( "Executing the linker command line for target: {:?}", target_name @@ -75,6 +88,11 @@ pub fn run_targets_generated_commands( env_vars, target_data, )?; + log::debug!( + "Took {:?} in process the linker cmd line for target: {}", + linker_time.elapsed(), + target_name.name() + ); } Ok(()) @@ -121,11 +139,6 @@ where T: IntoIterator + std::fmt::Display + std::marker::Copy, S: AsRef, { - /* fn execute_command( - model: &ZorkModel, - arguments: &Arguments, - env_vars: &EnvVars, - ) -> Result { */ let compiler = model.compiler.cpp_compiler; log::trace!( "[{compiler}] - Executing command => {:?}", diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 47d7a586..805a3d68 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -303,6 +303,7 @@ pub mod worker { CppCompiler::GCC => &cache.compilers_metadata.gcc.env_vars, }; + let modules_time = Instant::now(); executors::run_modules_generated_commands( program_data, &general_args, @@ -310,6 +311,10 @@ pub mod worker { &mut generated_commands.modules, env_vars, )?; + log::debug!( + "Took {:?} in analyze and run the generated modules commands", + modules_time.elapsed() + ); match cli_args.command { Command::Build => executors::run_targets_generated_commands( @@ -320,27 +325,37 @@ pub mod worker { &generated_commands.modules, env_vars, ), // TODO: group the duplicated calls - Command::Run | Command::Test => match executors::run_targets_generated_commands( - program_data, - &general_args, - &compiler_specific_shared_args, - &mut generated_commands.targets, - &generated_commands.modules, - env_vars, - ) { - Ok(_) => { - for target_name in generated_commands.targets.keys() { - executors::autorun_generated_binary( - &program_data.compiler.cpp_compiler, - &program_data.build.output_dir, - target_name.name(), - )? - } - return Ok(()); - } - Err(e) => Err(e), - }?, + Command::Run | Command::Test => { + let rgtct = Instant::now(); + let rgtc = executors::run_targets_generated_commands( + program_data, + &general_args, + &compiler_specific_shared_args, + &mut generated_commands.targets, + &generated_commands.modules, + env_vars, + ); + log::debug!( + "Took {:?} in analyze and run the current target", + rgtct.elapsed() + ); + + match rgtc { + Ok(_) => { + for target_name in generated_commands.targets.keys() { + executors::autorun_generated_binary( + &program_data.compiler.cpp_compiler, + &program_data.build.output_dir, + target_name.name(), + )? + } + + return Ok(()); + } + Err(e) => Err(e), + }? + } _ => todo!("{}", error_messages::CLI_ARGS_CMD_NEW_BRANCH), } } From 8fd6f64ca83ddbc782a2cd039d816576b22d27ca Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 26 Jul 2024 12:20:14 +0200 Subject: [PATCH 55/73] feat: Reducing cognitive load on the run_zork fn --- zork++/src/lib/lib.rs | 242 +++++------------- zork++/src/lib/utils/constants.rs | 2 + .../template/resources/zork_example.toml | 8 +- 3 files changed, 75 insertions(+), 177 deletions(-) diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 805a3d68..09f55794 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -15,10 +15,11 @@ pub mod utils; /// without having to do fancy work about checking the /// data sent to stdout/stderr pub mod worker { + use crate::config_file; use crate::config_file::ZorkConfigFile; use crate::domain::target::Target; use crate::project_model; - use crate::{config_file, utils::fs::get_project_root_absolute_path}; + use std::path::PathBuf; use std::{fs, path::Path, time::Instant}; use crate::utils::constants::{dir_names, error_messages, ZORK}; @@ -41,173 +42,17 @@ pub mod worker { /// The main work of the project. Runs the tasks /// inputted in the CLI - /* pub fn run_zork(cli_args: &CliArgs) -> std::result::Result<(), Report> { - let project_root = cli_args - .root - .as_deref() - .map(Path::new) - .unwrap_or(Path::new(".")); - let abs_project_root = get_project_root_absolute_path(project_root)?; - // TODO: falta o do project model, gañans! - if let Command::New { - ref name, - git, - compiler, - template, - } = cli_args.command - { - // TODO: pass here the driver's path? so it's already configured on the autogenerated - // zork.toml file? - return create_templated_project( - &abs_project_root, - name, - git, - compiler.into(), - template, - ); - }; - - let config_files: Vec = find_config_files(project_root, &cli_args.match_files)?; - - for config_file in config_files { - let cfg_path = &config_file.path; - log::debug!( - "Launching a Zork++ work event for the configuration file: {:?}", - cfg_path, - ); - let raw_file = fs::read_to_string(cfg_path) - .with_context(|| format!("{}: {:?}", error_messages::READ_CFG_FILE, cfg_path))?; - - let config: ZorkConfigFile<'_> = config_file::zork_cfg_from_file(raw_file.as_str()) - .with_context(|| error_messages::PARSE_CFG_FILE)?; - - create_output_directory(&config, &abs_project_root)?; - - let mut cache: ZorkCache<'_> = cache::load(&config, cli_args, &abs_project_root)?; - - let program_data: ZorkModel<'_> = - if config_file.last_time_modified > cache.metadata.last_program_execution { - cache.metadata.save_project_model = true; - utils::reader::build_model(config, cli_args, &abs_project_root)? - } else { - log::debug!("Loading the ZorkModel from the cache"); - project_model::load(&cache)? - }; - - map_model_targets_to_cache(&program_data, &mut cache); - - let generate_commands_ts = Instant::now(); - // Generates the commands for every translation unit and/or checks on successive iterations - // of the program it any of them has been modified so the files must be marked to be - // rebuilt again - generate_commands(&program_data, &mut cache, cli_args) - .with_context(|| error_messages::FAILURE_GENERATING_COMMANDS)?; - - log::debug!( - "Zork++ took a total of {:?} ms on handling the generated commands", - generate_commands_ts.elapsed().as_millis() - ); - {let tmp = do_main_work_based_on_cli_input(cli_args, &program_data, &mut cache).with_context(|| { - format!( - "{}: {:?}", - error_messages::FAILED_BUILD_FOR_CFG_FILE, - cfg_path - ) - })?; - } - - cache.save(&program_data, cli_args)? - - } - - - - Ok(()) - } - - /// Helper for reduce the cyclomatic complexity of the main fn. - /// - /// Contains the main calls to the generation of the compilers commands lines, - /// the calls to the process that runs those, the autorun the generated - /// binaries, the tests declared for the projects... - fn do_main_work_based_on_cli_input<'a, 'b: 'a>( - cli_args: &'b CliArgs, - program_data: &'b ZorkModel<'b>, - cache: &'b mut ZorkCache<'b>, - ) -> Result<()> { - - let generated_commands = &mut cache.generated_commands; - - let general_args = generated_commands - .general_args - .as_mut() - .with_context(|| error_messages::GENERAL_ARGS_NOT_FOUND)? - .get_args(); - - let compiler_specific_shared_args = generated_commands - .compiler_common_args - .as_mut() - .with_context(|| error_messages::COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND)? - .get_args(); - - let env_vars = match program_data.compiler.cpp_compiler { - CppCompiler::MSVC => &cache.compilers_metadata.msvc.env_vars, - CppCompiler::CLANG => &cache.compilers_metadata.clang.env_vars, - CppCompiler::GCC => &cache.compilers_metadata.gcc.env_vars, - }; - - executors::run_modules_generated_commands(program_data, &general_args, &compiler_specific_shared_args, &mut generated_commands.modules, &env_vars)?; - // TODO: cache save by not propagating Err with ? - - match cli_args.command { - Command::Build => executors::run_targets_generated_commands(program_data, &general_args, &compiler_specific_shared_args, &mut generated_commands.targets, &env_vars), - Command::Run | Command::Test => { - // TODO: for target in cache and filtered by cmdargs - match executors::run_targets_generated_commands(program_data, &general_args, &compiler_specific_shared_args, &mut generated_commands.targets, &env_vars) { - Ok(_) => { - for (target_name, target_data) in generated_commands.targets.iter() { - executors::autorun_generated_binary( - &program_data.compiler.cpp_compiler, - &program_data.build.output_dir, - target_name.value(), - // &program_data.executable.executable_name, - )? - } - return Ok(()); - } - Err(e) => Err(e), - } - }, - _ => todo!("{}", error_messages::CLI_ARGS_CMD_NEW_BRANCH), - } - } - */ - pub fn run_zork(cli_args: &CliArgs) -> std::result::Result<(), Report> { - let project_root = cli_args - .root - .as_deref() - .map(Path::new) - .unwrap_or(Path::new(".")); - let abs_project_root = get_project_root_absolute_path(project_root)?; + let abs_project_root = determine_absolute_path_of_the_project_root(cli_args)?; - if let Command::New { - ref name, - git, - compiler, - template, - } = cli_args.command - { - return create_templated_project( - &abs_project_root, - name, - git, - compiler.into(), - template, - ); + // If this run is just for create a new C++ project with the given Zork++ projects creation + // by template, create it and exit + if is_template_creation_then_create(cli_args, &abs_project_root)? { + return Ok(()); }; - let config_files: Vec = find_config_files(project_root, &cli_args.match_files)?; + let config_files: Vec = + find_config_files(&abs_project_root, &cli_args.match_files)?; for config_file in config_files { let cfg_path = &config_file.path; @@ -225,15 +70,13 @@ pub mod worker { let mut cache: ZorkCache<'_> = cache::load(&config, cli_args, &abs_project_root)?; - let program_data: ZorkModel<'_> = - if config_file.last_time_modified > cache.metadata.last_program_execution { - cache.metadata.save_project_model = true; - utils::reader::build_model(config, cli_args, &abs_project_root)? - } else { - log::debug!("Loading the ZorkModel from the cache"); - project_model::load(&cache)? - }; - + let program_data: ZorkModel<'_> = load_zork_model( + &mut cache, + &config_file, + config, + cli_args, + &abs_project_root, + )?; map_model_targets_to_cache(&program_data, &mut cache); let generate_commands_ts = Instant::now(); @@ -261,6 +104,25 @@ pub mod worker { Ok(()) } + /// Inspects the [`CliArgs`] main passed argument, and if it's [`Command::New`] just creates a + /// new *C++* project at the *abs_project_root* and exits + fn is_template_creation_then_create( + cli_args: &CliArgs, + abs_project_root: &Path, + ) -> Result { + if let Command::New { + ref name, + git, + compiler, + template, + } = cli_args.command + { + create_templated_project(abs_project_root, name, git, compiler.into(), template)?; + return Ok(true); + }; + Ok(false) + } + fn perform_main_work( cli_args: &CliArgs, program_data: &ZorkModel<'_>, @@ -360,6 +222,40 @@ pub mod worker { } } + /// Resolves the full path of the location of the project's root on the fs. If the `--root` + /// [`CliArgs`] arg is present, it will be used as the project root path, otherwise, we will + /// assume that the project root is exactly in the same directory from where the *Zork++* + /// binary was invoked by the user + fn determine_absolute_path_of_the_project_root(cli_args: &CliArgs) -> Result { + let project_root = cli_args + .root + .as_deref() + .map(Path::new) + .unwrap_or(Path::new(".")); + + utils::fs::get_project_root_absolute_path(project_root) + .with_context(|| error_messages::FAILURE_GATHERING_PROJECT_ROOT_ABS_PATH) + } + + /// Helper function to load the data of a concrete [`ZorkConfigFile`] into a [`ZorkModel`], + /// which is the ultimate data structure that holds the read only information about the user + /// input in a more concise way that the config file struct. + fn load_zork_model<'a>( + cache: &mut ZorkCache<'a>, + meta_config_file: &ConfigFile, + zork_config_file: ZorkConfigFile<'a>, + cli_args: &'a CliArgs, + abs_project_root: &Path, + ) -> Result> { + if meta_config_file.last_time_modified > cache.metadata.last_program_execution { + cache.metadata.save_project_model = true; + utils::reader::build_model(zork_config_file, cli_args, abs_project_root) + } else { + log::debug!("Loading the ZorkModel from the cache"); + project_model::load(cache) + } + } + /// Helper to map the user declared targets on the [`ZorkModel`], previously mapped from the /// [`ZorkConfigFile`] into the [`ZorkCache`], in order to avoid later calls with entry or insert /// which will be hidden on the code an harder to read for newcomers or after time without diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index 73398833..60d1637d 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -30,6 +30,8 @@ pub mod error_messages { "Failed to generated the commands for the project"; pub const FAILED_BUILD_FOR_CFG_FILE: &str = "Failed to build the project for the config file"; pub const FAILURE_CREATING_CACHE_FILE: &str = "Error creating the cache file"; + pub const FAILURE_GATHERING_PROJECT_ROOT_ABS_PATH: &str = + "An unexpected error happened while resolving the absolute path to the project root"; pub const FAILURE_CREATING_COMPILER_CACHE_DIR: &str = "Error creating the cache subdirectory for compiler"; pub const FAILURE_LOADING_CACHE: &str = "Failed to load the Zork++ cache"; diff --git a/zork++/src/lib/utils/template/resources/zork_example.toml b/zork++/src/lib/utils/template/resources/zork_example.toml index 8ffe0ed6..d50c3e91 100644 --- a/zork++/src/lib/utils/template/resources/zork_example.toml +++ b/zork++/src/lib/utils/template/resources/zork_example.toml @@ -12,12 +12,12 @@ std_lib = "LIBCPP" [build] output_dir = "out" -[executable] -executable_name = "" +[targets.executable] +output_name = "" sources = [ "*.cpp" ] -[tests] -tests_executable_name = "zork_proj_tests" +[targets.tests] +output_name = "zork_proj_tests" sources = [ "*.cpp" ] [modules] From a7e85493b9a34c80f9ee2ca8e3712deb047b6311 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 26 Jul 2024 12:23:28 +0200 Subject: [PATCH 56/73] fix: Use the new targets.target format on the template zork config files --- .../lib/utils/template/resources/zork_example_basic.toml | 8 ++++---- .../utils/template/resources/zork_example_basic_gcc.toml | 8 ++++---- .../utils/template/resources/zork_example_basic_msvc.toml | 8 ++++---- .../lib/utils/template/resources/zork_example_gcc.toml | 8 ++++---- .../lib/utils/template/resources/zork_example_msvc.toml | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/zork++/src/lib/utils/template/resources/zork_example_basic.toml b/zork++/src/lib/utils/template/resources/zork_example_basic.toml index 047d1b8e..64bc708a 100644 --- a/zork++/src/lib/utils/template/resources/zork_example_basic.toml +++ b/zork++/src/lib/utils/template/resources/zork_example_basic.toml @@ -12,12 +12,12 @@ std_lib = "LIBCPP" [build] output_dir = "out" -[executable] -executable_name = "" +[targets.executable] +output_name = "" sources = [ "*.cpp" ] -[tests] -tests_executable_name = "zork_proj_tests" +[targets.tests] +output_name = "zork_proj_tests" sources = [ "*.cpp" ] [modules] diff --git a/zork++/src/lib/utils/template/resources/zork_example_basic_gcc.toml b/zork++/src/lib/utils/template/resources/zork_example_basic_gcc.toml index cbed9090..25a6885f 100644 --- a/zork++/src/lib/utils/template/resources/zork_example_basic_gcc.toml +++ b/zork++/src/lib/utils/template/resources/zork_example_basic_gcc.toml @@ -11,12 +11,12 @@ cpp_standard = "23" [build] output_dir = "out" -[executable] -executable_name = "" +[targets.executable] +output_name = "" sources = [ "*.cpp" ] -[tests] -tests_executable_name = "zork_proj_tests" +[targets.tests] +output_name = "zork_proj_tests" sources = [ "*.cpp" ] [modules] diff --git a/zork++/src/lib/utils/template/resources/zork_example_basic_msvc.toml b/zork++/src/lib/utils/template/resources/zork_example_basic_msvc.toml index ab3033d4..01d2e52f 100644 --- a/zork++/src/lib/utils/template/resources/zork_example_basic_msvc.toml +++ b/zork++/src/lib/utils/template/resources/zork_example_basic_msvc.toml @@ -11,12 +11,12 @@ cpp_standard = "latest" [build] output_dir = "out" -[executable] -executable_name = "" +[targets.executable] +output_name = "" sources = [ "*.cpp" ] -[tests] -tests_executable_name = "zork_proj_tests" +[targets.tests] +output_name = "zork_proj_tests" sources = [ "*.cpp" ] [modules] diff --git a/zork++/src/lib/utils/template/resources/zork_example_gcc.toml b/zork++/src/lib/utils/template/resources/zork_example_gcc.toml index 4671202f..d062370a 100644 --- a/zork++/src/lib/utils/template/resources/zork_example_gcc.toml +++ b/zork++/src/lib/utils/template/resources/zork_example_gcc.toml @@ -11,12 +11,12 @@ cpp_standard = "23" [build] output_dir = "out" -[executable] -executable_name = "" +[targets.executable] +output_name = "" sources = [ "*.cpp" ] -[tests] -tests_executable_name = "zork_proj_tests" +[targets.tests] +output_name = "zork_proj_tests" sources = [ "*.cpp" ] [modules] diff --git a/zork++/src/lib/utils/template/resources/zork_example_msvc.toml b/zork++/src/lib/utils/template/resources/zork_example_msvc.toml index 54dbfd49..de174c25 100644 --- a/zork++/src/lib/utils/template/resources/zork_example_msvc.toml +++ b/zork++/src/lib/utils/template/resources/zork_example_msvc.toml @@ -11,12 +11,12 @@ cpp_standard = "latest" [build] output_dir = "out" -[executable] -executable_name = "" +[targets.executable] +output_name = "" sources = [ "*.cpp" ] -[tests] -tests_executable_name = "zork_proj_tests" +[targets.tests] +output_name = "zork_proj_tests" sources = [ "*.cpp" ] [modules] From 145c30bf25c425e8b051a35e9311fb33eb4cd4d0 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 26 Jul 2024 17:14:08 +0200 Subject: [PATCH 57/73] fix: missing 'compile but no link' argument on Clang and GCC --- zork++/src/lib/cli/output/executors.rs | 23 ++++--------- zork++/src/lib/compiler/mod.rs | 3 -- .../src/lib/domain/commands/command_lines.rs | 34 ------------------- zork++/src/lib/domain/target.rs | 1 - zork++/src/lib/utils/reader.rs | 4 +-- 5 files changed, 9 insertions(+), 56 deletions(-) diff --git a/zork++/src/lib/cli/output/executors.rs b/zork++/src/lib/cli/output/executors.rs index 628a2993..20bb615c 100644 --- a/zork++/src/lib/cli/output/executors.rs +++ b/zork++/src/lib/cli/output/executors.rs @@ -2,7 +2,6 @@ //! by Zork++ use std::ffi::OsStr; -use std::time::Instant; use std::{path::Path, process::ExitStatus}; use crate::cache::EnvVars; @@ -54,7 +53,6 @@ pub fn run_targets_generated_commands( ); // Send to build to the compiler the sources declared for the current iteration target - let srcs_time = Instant::now(); for source in target_data .sources .iter_mut() @@ -68,13 +66,7 @@ pub fn run_targets_generated_commands( source, )?; } - log::debug!( - "Took {:?} in process the sources for target: {}", - srcs_time.elapsed(), - target_name.name() - ); - let linker_time = Instant::now(); log::info!( "Executing the linker command line for target: {:?}", target_name @@ -88,11 +80,6 @@ pub fn run_targets_generated_commands( env_vars, target_data, )?; - log::debug!( - "Took {:?} in process the linker cmd line for target: {}", - linker_time.elapsed(), - target_name.name() - ); } Ok(()) @@ -176,7 +163,12 @@ mod helpers { env_vars: &HashMap, source: &mut SourceCommandLine<'_>, ) -> Result<()> { - let compile_but_dont_link = [Argument::from("/c")]; + let compile_but_dont_link: [Argument; 1] = + [Argument::from(match program_data.compiler.cpp_compiler { + CppCompiler::CLANG | CppCompiler::GCC => "-c", + CppCompiler::MSVC => "/c", + })]; + let args = general_args .iter() .chain(compiler_specific_shared_args.iter()) @@ -227,13 +219,11 @@ mod helpers { .iter() .chain(linker_args.iter()) .chain(compiler_specific_shared_args.iter()) - // .chain(linker_command_line.byproducts.iter()) .chain(linker_sources_byproducts) .chain(modules_byproducts) .collect::(); let r = execute_command(program_data, &args, env_vars); - target_data.linker.execution_result = TranslationUnitStatus::from(&r); if let Err(e) = r { @@ -297,6 +287,7 @@ mod helpers { return Err(err); } } + Ok(()) } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 4f1c51a0..f506f345 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -560,9 +560,6 @@ mod sources { arguments.push(format!("{fo}{}", obj_file.display())); arguments.push(source.path()); - log::warn!("Adding target entry for: {:?}", target_identifier); - log::warn!("Targets status: {:?}", cache.generated_commands.targets); - let command_line = SourceCommandLine::new(source, arguments, obj_file); cache .generated_commands diff --git a/zork++/src/lib/domain/commands/command_lines.rs b/zork++/src/lib/domain/commands/command_lines.rs index 18643c33..6bda9df8 100644 --- a/zork++/src/lib/domain/commands/command_lines.rs +++ b/zork++/src/lib/domain/commands/command_lines.rs @@ -3,8 +3,6 @@ use crate::domain::commands::arguments::{Argument, Arguments}; use crate::domain::target::{Target, TargetIdentifier}; use crate::domain::translation_unit::{TranslationUnit, TranslationUnitStatus}; use crate::project_model::compiler::CppCompiler; -use crate::utils::constants::error_messages; -use color_eyre::eyre::{ContextCompat, Result}; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use std::fmt::Debug; @@ -56,12 +54,7 @@ impl<'a> SourceCommandLine<'a> { #[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)] pub struct LinkerCommandLine<'a> { - // TODO: review if we do need this struct yet, since target does - // the same - link_modules: bool, // TODO: pending pub target: Argument<'a>, - pub modules_byproducts: Arguments<'a>, - pub byproducts: Arguments<'a>, pub extra_args: Arguments<'a>, pub execution_result: TranslationUnitStatus, } @@ -75,13 +68,6 @@ impl<'a> LinkerCommandLine<'a> { CppCompiler::MSVC => vec![self.target.clone()], } } - - /// Saves the path at which a compilation product of any translation unit will be placed, - /// in order to add it to the files that will be linked to generate the final product - /// in the two-phase compilation model - pub fn add_byproduct_path(&mut self, path: PathBuf) { - self.byproducts.push(path); - } } /// Holds the generated command line arguments for a concrete compiler @@ -120,24 +106,4 @@ impl<'a> Commands<'a> { .chain(self.modules.interfaces.as_mut_slice().iter_mut()) .chain(self.modules.implementations.as_mut_slice().iter_mut()) } - - // TODO: unused, think that doesn't makes sense anymore with the current architechture - pub fn add_linker_file_path( - &mut self, - target_identifier: &TargetIdentifier<'a>, - path: PathBuf, - ) -> Result<()> { - self.targets - .get_mut(target_identifier) - .with_context(|| { - format!( - "{}: {:?}", - error_messages::TARGET_ENTRY_NOT_FOUND, - target_identifier - ) - })? - .linker - .add_byproduct_path(path); - Ok(()) - } } diff --git a/zork++/src/lib/domain/target.rs b/zork++/src/lib/domain/target.rs index ae12a62c..62ed81f1 100644 --- a/zork++/src/lib/domain/target.rs +++ b/zork++/src/lib/domain/target.rs @@ -9,7 +9,6 @@ use std::borrow::Cow; /// The final product that will be made after the building process #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct Target<'a> { - // pub identifier: Cow<'a, str>, pub sources: Vec>, pub linker: LinkerCommandLine<'a>, pub kind: TargetKind, diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index ceaf07dc..c1b7b68b 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -407,7 +407,7 @@ mod test { output_name: "zork".into(), sources: SourceSet { sources: vec![SourceFile { - path: PathBuf::default(), + path: abs_path_for_mock.clone(), file_stem: Cow::Borrowed("main"), extension: Cow::Borrowed("cpp"), }], @@ -422,7 +422,7 @@ mod test { output_name: "zork_tests".into(), sources: SourceSet { sources: vec![SourceFile { - path: PathBuf::default(), + path: abs_path_for_mock.clone(), file_stem: Cow::Borrowed("tests_main"), extension: Cow::Borrowed("cpp"), }], From f8ada584bbe5dbdad31e088fed56dea883a23975 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 26 Jul 2024 17:57:12 +0200 Subject: [PATCH 58/73] fix: Missing byproduct path on the SystemModule kind of translation unit This bug was causing to fail the linkage step, since the linker was receiving a ::default()(ed) Argument, which evaluates to "" --- zork++/src/lib/compiler/mod.rs | 54 ++++++------------- .../src/lib/domain/commands/command_lines.rs | 36 ++++++------- zork++/test/test.rs | 2 +- 3 files changed, 35 insertions(+), 57 deletions(-) diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index f506f345..aead441c 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -46,13 +46,13 @@ pub fn generate_commands<'a>( // Pre-tasks if model.compiler.cpp_compiler != CppCompiler::MSVC && !model.modules.sys_modules.is_empty() { - generate_sys_modules_commands(model, cache, cli_args)?; + generate_sys_modules_commands(model, cache)?; } // Translation units and linker // Generates commands for the modules - process_modules(model, cache, cli_args)?; + process_modules(model, cache)?; // Generate commands for the declared targets process_targets(model, cache, cli_args)?; @@ -91,12 +91,10 @@ fn generate_modular_stdlibs_cmds<'a>(model: &'a ZorkModel<'a>, cache: &mut ZorkC fn generate_sys_modules_commands<'a>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, - cli_args: &'a CliArgs, ) -> Result<()> { process_kind_translation_units( model, cache, - cli_args, &model.modules.sys_modules, TranslationUnitKind::SystemHeader, ) @@ -105,18 +103,13 @@ fn generate_sys_modules_commands<'a>( /// The procedure that takes care of generating the [`SourceCommandLine`] to build the user's declared /// C++ standard names modules -fn process_modules<'a>( - model: &'a ZorkModel<'a>, - cache: &mut ZorkCache<'a>, - cli_args: &'a CliArgs, -) -> Result<()> { +fn process_modules<'a>(model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>) -> Result<()> { let modules = &model.modules; log::info!("Generating the commands for the module interfaces and partitions..."); process_kind_translation_units( model, cache, - cli_args, &modules.interfaces, TranslationUnitKind::ModuleInterface, ) @@ -126,7 +119,6 @@ fn process_modules<'a>( process_kind_translation_units( model, cache, - cli_args, &modules.implementations, TranslationUnitKind::ModuleImplementation, ) @@ -138,14 +130,16 @@ fn process_modules<'a>( fn process_targets<'a>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, - cli_args: &'a CliArgs, + _cli_args: &'a CliArgs, ) -> Result<()> { for target in &model.targets { + // TODO: filter targets on cli input // 2nd - Generate the commands for the non-module sources - generate_sources_cmds_args(model, cache, cli_args, target)?; + generate_sources_cmds_args(model, cache, target)?; // 3rd - Generate the linker command for the 'target' declared by the user - generate_linkage_targets_commands(model, cache, cli_args, target); + generate_linkage_targets_commands(model, cache, target)?; } + Ok(()) } @@ -159,7 +153,6 @@ fn process_targets<'a>( fn generate_sources_cmds_args<'a>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, - cli_args: &'a CliArgs, target: (&'a TargetIdentifier<'a>, &'a TargetModel<'a>), ) -> Result<()> { log::info!( @@ -173,7 +166,6 @@ fn generate_sources_cmds_args<'a>( process_kind_translation_units( model, cache, - cli_args, &target_data.sources.sources, // FIX: the sources.sources access with the repeated field // names TranslationUnitKind::SourceFile(target_identifier), @@ -185,26 +177,13 @@ fn generate_sources_cmds_args<'a>( fn generate_linkage_targets_commands<'a>( model: &'a ZorkModel<'_>, cache: &mut ZorkCache<'a>, - _cli_args: &'a CliArgs, target: (&'a TargetIdentifier<'a>, &'a TargetModel<'a>), -) { +) -> Result<()> { log::info!( "Generating the linker command line for target: {:?}", &target.0 ); - generate_linker_general_command_line_args(model, cache, target); -} -/// Generates the general command line arguments for the desired target -/// -/// **implementation note:** All the final byproducts of the compiled translation units, the object -/// files paths, are added in place to the linker args member when they're created, so we can avoid -/// to clone them everytime we create a new [`SourceCommandLine`] for a given translation unit -pub fn generate_linker_general_command_line_args<'a>( - model: &ZorkModel<'_>, - cache: &mut ZorkCache<'a>, - target: (&'a TargetIdentifier<'a>, &'a TargetModel<'a>), -) { let target_identifier = target.0; let target_details = target.1; @@ -212,7 +191,7 @@ pub fn generate_linker_general_command_line_args<'a>( .generated_commands .targets .get_mut(target_identifier) - .unwrap() // TODO: care, better use with_context, even tho this is a non-failable unwrap + .with_context(|| error_messages::FAILURE_TARGET_SOURCES)? .linker; let compiler = &model.compiler.cpp_compiler; @@ -241,6 +220,8 @@ pub fn generate_linker_general_command_line_args<'a>( .extra_args .extend_from_to_argument_slice(&target_details.extra_args); } + + Ok(()) } /// The core procedure of the commands generation process. @@ -253,12 +234,11 @@ pub fn generate_linker_general_command_line_args<'a>( fn process_kind_translation_units<'a, T: TranslationUnit<'a>>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, - cli_args: &'a CliArgs, translation_units: &'a [T], for_kind: TranslationUnitKind<'a>, ) -> Result<()> { for translation_unit in translation_units.iter() { - process_kind_translation_unit(model, cache, cli_args, translation_unit, &for_kind)? + process_kind_translation_unit(model, cache, translation_unit, &for_kind)? } Ok(()) @@ -267,8 +247,6 @@ fn process_kind_translation_units<'a, T: TranslationUnit<'a>>( fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>, - _cli_args: &'a CliArgs, // TODO: review if it will be further required on other evolutions of - // the codebase translation_unit: &'a T, for_kind: &TranslationUnitKind<'a>, ) -> Result<()> { @@ -329,7 +307,7 @@ mod modules { use crate::cache::ZorkCache; use crate::compiler::helpers; use crate::compiler::helpers::generate_bmi_file_path; - use crate::domain::commands::arguments::{clang_args, msvc_args, Argument, Arguments}; + use crate::domain::commands::arguments::{clang_args, msvc_args, Arguments}; use crate::domain::commands::command_lines::SourceCommandLine; use crate::domain::translation_unit::{TranslationUnit, TranslationUnitStatus}; use crate::project_model::compiler::{CppCompiler, StdLibMode}; @@ -482,14 +460,14 @@ mod modules { }; let cmd = SourceCommandLine { - directory: generated_bmi_path, // TODO: we are using the generated byproduct as the path for checking for its + directory: generated_bmi_path.clone(), // TODO: we are using the generated byproduct as the path for checking for its // existence instead of the system header one while we don't implement the feature that inspects where the standard // libraries lives. Isn't ideal, but serves the basic purpose of regenerating the commands when they it's needed, and // system headers aren't unlikely to change (unless an stdlib update) filename: sys_module.to_string(), args, status: TranslationUnitStatus::PendingToBuild, - byproduct: /* TODO:!!!!!!: */ Argument::default() + byproduct: generated_bmi_path.into(), }; cache.generated_commands.modules.system_modules.push(cmd); } diff --git a/zork++/src/lib/domain/commands/command_lines.rs b/zork++/src/lib/domain/commands/command_lines.rs index 6bda9df8..22d5007a 100644 --- a/zork++/src/lib/domain/commands/command_lines.rs +++ b/zork++/src/lib/domain/commands/command_lines.rs @@ -8,6 +8,24 @@ use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::path::{Path, PathBuf}; +/// Holds the generated command line arguments for a concrete compiler +#[derive(Serialize, Deserialize, Default, Debug)] +pub struct Commands<'a> { + pub general_args: Option>, + pub compiler_common_args: Option>, + pub modules: ModulesCommands<'a>, + pub targets: IndexMap, Target<'a>>, +} + +#[derive(Serialize, Deserialize, Default, Debug)] +pub struct ModulesCommands<'a> { + pub cpp_stdlib: Option>, + pub c_compat_stdlib: Option>, + pub system_modules: Vec>, + pub interfaces: Vec>, + pub implementations: Vec>, +} + /// Type for representing the command line that will be sent to the target compiler, and /// store its different components /// @@ -70,24 +88,6 @@ impl<'a> LinkerCommandLine<'a> { } } -/// Holds the generated command line arguments for a concrete compiler -#[derive(Serialize, Deserialize, Default, Debug)] -pub struct Commands<'a> { - pub general_args: Option>, - pub compiler_common_args: Option>, - pub modules: ModulesCommands<'a>, - pub targets: IndexMap, Target<'a>>, -} - -#[derive(Serialize, Deserialize, Default, Debug)] -pub struct ModulesCommands<'a> { - pub cpp_stdlib: Option>, - pub c_compat_stdlib: Option>, - pub system_modules: Vec>, - pub interfaces: Vec>, - pub implementations: Vec>, -} - impl<'a> Commands<'a> { /// Returns a [std::iter::Chain] (behind the opaque impl clause return type signature) /// which points to all the generated commands for the two variants of the compilers vendors C++ modular diff --git a/zork++/test/test.rs b/zork++/test/test.rs index 5d4efc1f..fa6ebfd4 100644 --- a/zork++/test/test.rs +++ b/zork++/test/test.rs @@ -219,7 +219,7 @@ mod local_env_tests { /// use a debugger to figure out what our changes are doing and how are affecting the codebase. #[test] #[ignore] - fn test_local_clang_full_process_manually_by_specifying_the_project_root_on_linux() { + fn test_local_clang_full_process_manually_by_specifying_the_project_root() { // Using env::home_dir because this test should be Unix specific // For any developer, change the path to whatever C++ project based on modules // you want to test Zork++ against From d8f2160e2fd1ec6707ddda62fcc085baf44f48ca Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 26 Jul 2024 19:43:12 +0200 Subject: [PATCH 59/73] feat: project root cfg attribute will be used as a joinable path to make the user's declared files easier to read, reducing the amount of directories per file that should be passed to the configuration file --- zork++/src/lib/cli/input/mod.rs | 2 +- zork++/src/lib/compiler/mod.rs | 6 +++--- zork++/src/lib/config_file/compiler.rs | 1 - zork++/src/lib/config_file/project.rs | 4 ++-- zork++/src/lib/project_model/project.rs | 2 +- zork++/src/lib/utils/reader.rs | 8 ++++---- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/zork++/src/lib/cli/input/mod.rs b/zork++/src/lib/cli/input/mod.rs index 562b1899..cf7beadf 100644 --- a/zork++/src/lib/cli/input/mod.rs +++ b/zork++/src/lib/cli/input/mod.rs @@ -97,7 +97,7 @@ pub enum TemplateValues { } /// [`CppCompiler`] The C++ compilers available within Zork++ as a command line argument for the `new` argument -/// TODO Possible future interesting on support the Intel's C++ compiler? +/// TODO: Possible future interesting on support the Intel's C++ compiler? #[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)] pub enum CppCompiler { CLANG, diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index aead441c..e942e4b1 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -44,15 +44,15 @@ pub fn generate_commands<'a>( // Build the std library as a module generate_modular_stdlibs_cmds(model, cache); - // Pre-tasks + // System headers as modules if model.compiler.cpp_compiler != CppCompiler::MSVC && !model.modules.sys_modules.is_empty() { generate_sys_modules_commands(model, cache)?; } - // Translation units and linker - // Generates commands for the modules process_modules(model, cache)?; + + // Translation units and linker // Generate commands for the declared targets process_targets(model, cache, cli_args)?; diff --git a/zork++/src/lib/config_file/compiler.rs b/zork++/src/lib/config_file/compiler.rs index 392cd1d9..c088ff90 100644 --- a/zork++/src/lib/config_file/compiler.rs +++ b/zork++/src/lib/config_file/compiler.rs @@ -91,7 +91,6 @@ pub enum CppCompiler { MSVC, #[serde(alias = "GCC", alias = "Gcc", alias = "gcc")] GCC, - // Possible future interesting on support the Intel's C++ compiler? } // Clippy warns to prefer implementing the From trait instead of Into. diff --git a/zork++/src/lib/config_file/project.rs b/zork++/src/lib/config_file/project.rs index edc22c1c..5450a2f9 100644 --- a/zork++/src/lib/config_file/project.rs +++ b/zork++/src/lib/config_file/project.rs @@ -25,7 +25,7 @@ use serde::{Deserialize, Serialize}; /// assert_eq!(config.name, "Zork++ serde tests"); /// assert_eq!(config.authors, Some(vec!["zerodaycode.gz@gmail.com"])); /// assert_eq!(config.compilation_db, Some(true)); -/// assert_eq!(config.project_root, None); +/// assert_eq!(config.code_root, None); /// ``` /// /// > Note: TOML table are toml commented (#) to allow us to parse @@ -45,5 +45,5 @@ pub struct ProjectAttribute<'a> { pub authors: Option>, pub compilation_db: Option, #[serde(borrow)] - pub project_root: Option<&'a str>, + pub code_root: Option<&'a str>, } diff --git a/zork++/src/lib/project_model/project.rs b/zork++/src/lib/project_model/project.rs index b96d37a1..d358e6b8 100644 --- a/zork++/src/lib/project_model/project.rs +++ b/zork++/src/lib/project_model/project.rs @@ -7,5 +7,5 @@ pub struct ProjectModel<'a> { pub name: Cow<'a, str>, pub authors: Vec>, pub compilation_db: bool, - pub project_root: Option>, + pub code_root: Option>, } diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index c1b7b68b..bd1ff6e6 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -56,7 +56,7 @@ pub struct ConfigFile { /// This function fails if there's no configuration file /// (or isn't present in any directory of the project) pub fn find_config_files( - base_path: &Path, // TODO: create the cfg arg to specifically receive where's located the + base_path: &Path, // TODO:(Workspaces) create the cfg arg to specifically receive where's located the // user's Zork config files if they're not in the root of the project // nor matches the tree structure from user's root cmd arg value filename_match: &Option, @@ -65,7 +65,7 @@ pub fn find_config_files( let mut files = vec![]; for e in WalkDir::new(base_path) - .max_depth(2) // TODO: so, max_depth should be zero when the cfg arg is ready + .max_depth(2) // TODO:(Workspaces) so, max_depth should be zero when the cfg arg is ready .into_iter() .filter_map(|e| e.ok()) { @@ -129,7 +129,7 @@ fn assemble_project_model(config: ProjectAttribute) -> ProjectModel { .collect::>() }), compilation_db: config.compilation_db.unwrap_or_default(), - project_root: config.project_root.map(Cow::Borrowed), + code_root: config.code_root.map(Cow::Borrowed), } } @@ -437,7 +437,7 @@ mod test { name: "Zork++".into(), authors: vec!["zerodaycode.gz@gmail.com".into()], compilation_db: true, - project_root: None, + code_root: None, }, compiler: CompilerModel { cpp_compiler: CppCompiler::CLANG, From faf665495774ccf7f887d15c9e565cff88c88915 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 26 Jul 2024 19:44:11 +0200 Subject: [PATCH 60/73] fix: code root is joined to the resolved absolute path when declared efficiently --- README.md | 84 ++++++++++++++++++--------------- zork++/src/lib/domain/target.rs | 18 +++++++ zork++/src/lib/lib.rs | 15 +----- zork++/src/lib/utils/reader.rs | 42 ++++++++++------- 4 files changed, 91 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index bc22fd4f..513f8272 100644 --- a/README.md +++ b/README.md @@ -228,18 +228,6 @@ std_lib = "libc++" # This concrete property will only be applied to Clang [build] output_dir = "./out" -[executable] -executable_name = "github-example" -sources = [ - "./github-example/*.cpp" -] - -[tests] -tests_executable_name = "zork_proj_tests" -sources = [ - "./github-example/*.cpp" -] - [modules] base_ifcs_dir = "./github-example/ifc" interfaces = [ @@ -251,6 +239,18 @@ implementations = [ { file = 'math2.cpp', dependencies = ['math'] } ] sys_modules = ['iostream'] + +[targets.executable] +executable_name = "github-example" +sources = [ + "./github-example/*.cpp" +] + +[targets.tests] +tests_executable_name = "zork_proj_tests" +sources = [ + "./github-example/*.cpp" +] ``` This is `toml` table syntax. You may choose whatever `toml` available syntax you prefer though. @@ -268,11 +268,11 @@ It is used to specify whether to use the `libc++` or `libstdc++` Standard librar - `[build]` ⇒ Specifics about how and where the generated files are dumped or treated. Here you specify where you want to create that directory. -- `[executable]` ⇒ Whenever `Zork++` sees this attribute, it knows that he must produce an executable. You must specify the name of the generated binary and the sources that will be taken into consideration to produce that executable. - - `[modules]` ⇒ The core section to instruct the compiler to work with `C++20 modules`. The most important attributes are the base path to the *interfaces* and *implementation* files (usually you don't want to spread your files elsewhere). `Zork++` only looks for your `interfaces` and `implementation` files in the respective directories. +- `[executable]` ⇒ Whenever `Zork++` sees this attribute, it knows that he must produce an executable. You must specify the name of the generated binary and the sources that will be taken into consideration to produce that executable. + - `[tests]` ⇒ The `tests` section allows you to run the tests written for your application in a convenient way. You only have to provide the sources, and you are ready to go! `Zork++` will take care of the rest. @@ -371,9 +371,10 @@ ZorkConfigFile { /// The [project] key ProjectAttribute { - name: &'a str + name: str authors: Option>, compilation_db : bool + code_root: str } /// The [compiler] key @@ -390,12 +391,24 @@ BuildAttribute { output_dir: Option, } -/// The [executable] key -ExecutableAttribute { - executable_name: Option, - sources_base_path: Option, +/// The [targets.] key +/// Any value after targets. will be used as the user's defined identifier for the target +/// Any entry in this format will be a new [`TargetAttribute`] which determines the final +/// product generated by `Zork++` +targets: Map + + +/// [`TargetAttribute`] - The type for holding the build details of every +/// user defined target +/// * `output_name`- The name with which the final byproduct will be generated +/// * `sources` - The sources to be included in the compilation of this target +/// * `extra_args` - Holds extra arguments that the user wants to introduce +/// * `kind` - Determined which type of byproduct will be generated (binary, library...) +TargetAttribute { + output_name: Option, sources: Option>, extra_args: Option>, + kind: Option, } /// [`ModulesAttribute`] - The core section to instruct the compiler to work with C++20 modules. The most important are the base path to the interfaces and implementation files @@ -412,15 +425,6 @@ ModulesAttribute { sys_modules: Option>, } -/// The [tests] key -TestsAttribute { - test_executable_name: Option, - sources_base_path: Option, - sources: Option>, - extra_args: Option>, -} -``` - ## A closer look on the `ModulesAttribute` key ```Rust @@ -495,7 +499,10 @@ For example: - `msvc` ⇒ (alias = "MSVC", alias = "Msvc", alias = "msvc") - `gcc` ⇒ (alias = "MSVC", alias = "Msvc", alias = "msvc") - The supported standard libraries to link against (`compiler.std_lib`, only applies to `Clang`) ⇒ `stdlibc++` or `libc++` - +- Supported kind of targets + - `executable` => (alias = "Executable", alias = "executable", alias = "exe") + - `static_lib` => (alias = "StaticLib", alias = "static lib", alias = "static-lib", alias = "static_lib", alias = "staticlib") + - `dylib` => (alias = "DynamicLib", alias = "dynamic lib", alias = "dyn-lib", alias = "dyn_lib", alias = "dylib") # :bookmark_tabs: The `Zork++` command line interface @@ -504,9 +511,9 @@ Our direct intention was to mimic the standard way of working with `Rust`'s `Car as it is a world-class tool well known and valued. To summarize, we are offering the following commands and arguments: -- `build` ⇒ just compiles the project -- `run` ⇒ compiles the project and then runs the generated binary -- `test` ⇒ compiles the project and then runs the test suite as described in the configuration file automatically +- `build` ⇒ just compiles the project for every target declared (unless filtered by cli args) +- `run` ⇒ compiles the project and then runs the generated binary for every target declared (unless filtered by cli args) +- `test` ⇒ compiles the project and then runs the binary generated for any [`targets.`] (unless filtered by cli args) - `new` ⇒ generates a new `C++20` onwards template project with a minimal configuration and a minimal setup. This command includes some arguments to make it more flexible, like: - `--name ` ⇒ the name of the autogenerated project @@ -516,6 +523,8 @@ a minimal setup. This command includes some arguments to make it more flexible, #### Arguments (they should be placed before the main subcommands described above) +- `--root` => instructs `Zork++` where is the working directory of the project, otherwise, the *cwd* from where +the `Zork++` binary was invokated will be used as the project's root - `--match-files` => Accepts an string value that will be used to perform a filter to the detected `Zork++` configuration files present in the project. Acts like the classical `contains` method, by checking that the value that you passed in is a substring of some of the detected config files. @@ -527,9 +536,7 @@ Controls which kind of `C++` code template is generated. - `-v` ⇒ Outputs more information to stdout. The classical `verbose` command line flag. You have until `-vv`, which is the maximum verbosity allowed, that will unlock the trace level logs. - `-c,`--clear-cache` ⇒ Clears the files in the cache, so, in the next iteration, cached items -must be processed again. This is useful after a lot of program iterations, when the cache starts -to slow down the build process. - +must be processed again. # :bookmark_tabs: Compilation Database @@ -564,10 +571,9 @@ In `Zork++`, you have this feature enabled if: - As alternative, you can use `import ;` This is, individually import some specific system header as a module. Needs an explicit pre-compilation process. This is supported by `Clang` and `GCC` (since we are not able to do an `import std` for `GCC` builds). - -- You're working with `MSVC`, you are able to use `import std.core`, as a compiler specific feature. But this will allow you to use import statements instead of `#include` directives. -In upcoming releases will we adapt to the real way on how Microsoft's compiler deals with this feature, so `Zork++` users will be able to correctly use `import std;` in their codebases with *MSVC*, not the workarounds existing up until this point. - +- `MSVC` => full support is available from `Zork++` *v0.9.0* onwards. No aditional user configuration required. +- `GCC` => We just don't know. We will be glad if some reader that knows about could give us some guidance in this regard. So there's +no `import std` feature nor workaround within `Zork++` for `GCC` # :balloon: Developers Guide diff --git a/zork++/src/lib/domain/target.rs b/zork++/src/lib/domain/target.rs index 62ed81f1..463e3801 100644 --- a/zork++/src/lib/domain/target.rs +++ b/zork++/src/lib/domain/target.rs @@ -12,6 +12,8 @@ pub struct Target<'a> { pub sources: Vec>, pub linker: LinkerCommandLine<'a>, pub kind: TargetKind, + #[serde(skip)] + pub enabled_for_current_program_iteration: bool, } impl<'a> Target<'a> { @@ -24,6 +26,7 @@ impl<'a> Target<'a> { sources: Vec::default(), linker: LinkerCommandLine::default(), kind, + enabled_for_current_program_iteration: true, } } } @@ -50,8 +53,23 @@ impl<'a> TargetIdentifier<'a> { #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Copy, Clone)] pub enum TargetKind { #[default] + #[serde(alias = "Executable", alias = "executable", alias = "exe")] Executable, + #[serde( + alias = "StaticLib", + alias = "static lib", + alias = "static-lib", + alias = "static_lib", + alias = "staticlib" + )] StaticLib, + #[serde( + alias = "DynamicLib", + alias = "dynamic lib", + alias = "dyn-lib", + alias = "dyn_lib", + alias = "dylib" + )] DyLib, } diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 09f55794..b29caea7 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -148,9 +148,7 @@ pub mod worker { let general_args = generated_commands .general_args .as_mut() - .with_context(|| error_messages::GENERAL_ARGS_NOT_FOUND)? // TODO: remove the optionals - // from the shared data on - // the flyweights + .with_context(|| error_messages::GENERAL_ARGS_NOT_FOUND)? .get_args(); let compiler_specific_shared_args = generated_commands @@ -165,7 +163,6 @@ pub mod worker { CppCompiler::GCC => &cache.compilers_metadata.gcc.env_vars, }; - let modules_time = Instant::now(); executors::run_modules_generated_commands( program_data, &general_args, @@ -173,10 +170,6 @@ pub mod worker { &mut generated_commands.modules, env_vars, )?; - log::debug!( - "Took {:?} in analyze and run the generated modules commands", - modules_time.elapsed() - ); match cli_args.command { Command::Build => executors::run_targets_generated_commands( @@ -189,7 +182,6 @@ pub mod worker { ), // TODO: group the duplicated calls Command::Run | Command::Test => { - let rgtct = Instant::now(); let rgtc = executors::run_targets_generated_commands( program_data, &general_args, @@ -198,10 +190,6 @@ pub mod worker { &generated_commands.modules, env_vars, ); - log::debug!( - "Took {:?} in analyze and run the current target", - rgtct.elapsed() - ); match rgtc { Ok(_) => { @@ -262,6 +250,7 @@ pub mod worker { /// reading the codebase fn map_model_targets_to_cache<'a>(program_data: &ZorkModel<'a>, cache: &mut ZorkCache<'a>) { for (target_identifier, target_data) in program_data.targets.iter() { + // 1st - Check if there's any new target to add to the tracked ones if !cache .generated_commands .targets diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index bd1ff6e6..461703ab 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -99,13 +99,21 @@ pub fn build_model<'a>( absolute_project_root: &Path, ) -> Result> { let proj_name = config.project.name; - let project = assemble_project_model(config.project); + let project = assemble_project_model(config.project); let compiler = assemble_compiler_model(config.compiler, cli_args); let build = assemble_build_model(config.build, absolute_project_root); - let modules = assemble_modules_model(config.modules, absolute_project_root); - let targets = assemble_targets_model(config.targets, proj_name, absolute_project_root); + let code_root = PathBuf::from(absolute_project_root).join( + project + .code_root + .as_ref() + .map(|pr| pr.to_string()) + .unwrap_or_default(), + ); + + let modules = assemble_modules_model(config.modules, &code_root); + let targets = assemble_targets_model(config.targets, proj_name, &code_root); Ok(ZorkModel { project, @@ -169,7 +177,7 @@ fn assemble_build_model(config: Option, project_root: &Path) -> fn assemble_modules_model<'a>( config: Option>, - project_root: &Path, + code_root: &Path, ) -> ModulesModel<'a> { let modules = config.unwrap_or_default(); @@ -184,7 +192,7 @@ fn assemble_modules_model<'a>( .map(|ifcs| { ifcs.into_iter() .map(|m_ifc| -> ModuleInterfaceModel<'_> { - assemble_module_interface_model(m_ifc, &base_ifcs_dir, project_root) + assemble_module_interface_model(m_ifc, &base_ifcs_dir, code_root) }) .collect() }) @@ -202,7 +210,7 @@ fn assemble_modules_model<'a>( impls .into_iter() .map(|m_impl| { - assemble_module_implementation_model(m_impl, &base_impls_dir, project_root) + assemble_module_implementation_model(m_impl, &base_impls_dir, code_root) }) .collect() }) @@ -233,11 +241,12 @@ fn assemble_modules_model<'a>( fn assemble_module_interface_model<'a>( config: ModuleInterface<'a>, base_path: &Path, - project_root: &Path, + code_root: &Path, ) -> ModuleInterfaceModel<'a> { let cfg_file = config.file; - let file_path = Path::new(project_root).join(base_path).join(cfg_file); + let file_path = Path::new(code_root).join(base_path).join(cfg_file); + let module_name = if let Some(mod_name) = config.module_name { Cow::Borrowed(mod_name) } else { @@ -269,7 +278,7 @@ fn assemble_module_interface_model<'a>( fn assemble_module_implementation_model<'a>( config: ModuleImplementation<'a>, base_path: &Path, - project_root: &Path, + code_root: &Path, ) -> ModuleImplementationModel<'a> { let mut dependencies = config .dependencies @@ -278,7 +287,8 @@ fn assemble_module_implementation_model<'a>( .map(Cow::Borrowed) .collect::>>(); - let file_path = Path::new(project_root).join(base_path).join(config.file); + let file_path = Path::new(code_root).join(base_path).join(config.file); + if dependencies.is_empty() { let last_dot_index = config.file.rfind('.'); if let Some(idx) = last_dot_index { @@ -304,14 +314,14 @@ fn assemble_module_implementation_model<'a>( fn assemble_targets_model<'a>( targets: IndexMap<&'a str, TargetAttribute<'a>>, project_name: &'a str, - absolute_project_root: &Path, + code_root: &Path, ) -> IndexMap, TargetModel<'a>> { targets .into_iter() .map(|(k, v)| { ( TargetIdentifier(Cow::Borrowed(k)), - assemble_target_model(v, project_name, absolute_project_root), + assemble_target_model(v, project_name, code_root), ) }) .collect() @@ -320,7 +330,7 @@ fn assemble_targets_model<'a>( fn assemble_target_model<'a>( target_config: TargetAttribute<'a>, project_name: &'a str, - absolute_project_root: &Path, + code_root: &Path, ) -> TargetModel<'a> { let sources = target_config .sources @@ -328,7 +338,7 @@ fn assemble_target_model<'a>( .map(Cow::Borrowed) .collect(); - let sources = get_sources_for_target(sources, absolute_project_root); + let sources = get_sources_for_target(sources, code_root); let extra_args = target_config .extra_args @@ -346,11 +356,11 @@ fn assemble_target_model<'a>( /// Utilery function to map all the source files declared on the [`ZorkConfigFile::targets`] /// attribute to the domain model entity, including resolving any [`GlobPattern`] declared as /// any file on the input collection -fn get_sources_for_target<'a>(srcs: Vec>, project_root: &Path) -> SourceSet<'a> { +fn get_sources_for_target<'a>(srcs: Vec>, code_root: &Path) -> SourceSet<'a> { let sources = srcs .iter() .map(|src| { - let target_src = project_root.join(src.as_ref()); + let target_src = code_root.join(src.as_ref()); if src.contains('*') { Source::Glob(GlobPattern(target_src)) } else { From 9af3d7f31708a8054bb385022eb155e5c5040e30 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 4 Aug 2024 14:05:14 +0200 Subject: [PATCH 61/73] feat: Handling activation/deactivation of named targets via CLI argument --- README.md | 5 +- zork++/benches/benchmarks.rs | 2 +- zork++/src/lib/cache/mod.rs | 9 +- zork++/src/lib/cli/input/mod.rs | 13 +- zork++/src/lib/cli/output/executors.rs | 12 +- zork++/src/lib/compiler/mod.rs | 38 +++--- zork++/src/lib/domain/target.rs | 1 - zork++/src/lib/lib.rs | 168 ++++++++++++++++--------- zork++/src/lib/project_model/mod.rs | 5 +- zork++/src/lib/project_model/target.rs | 1 + zork++/src/lib/utils/constants.rs | 1 + zork++/src/lib/utils/reader.rs | 4 + 12 files changed, 160 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index 513f8272..2fe63e3f 100644 --- a/README.md +++ b/README.md @@ -524,7 +524,10 @@ a minimal setup. This command includes some arguments to make it more flexible, #### Arguments (they should be placed before the main subcommands described above) - `--root` => instructs `Zork++` where is the working directory of the project, otherwise, the *cwd* from where -the `Zork++` binary was invokated will be used as the project's root +the `Zork++` binary was invokated will be used as the project's root. +- `--targets` => filters the targets by its declared name on the `targets.` entry that will be +processed in the current iteration. Expected to be formatted as: `--targets=target1,target2,target3`. NOTE: Empty +whitespaces won't be trim so `target1, target2` will evaluate to ["target1", " target2"]. - `--match-files` => Accepts an string value that will be used to perform a filter to the detected `Zork++` configuration files present in the project. Acts like the classical `contains` method, by checking that the value that you passed in is a substring of some of the detected config files. diff --git a/zork++/benches/benchmarks.rs b/zork++/benches/benchmarks.rs index 6b8fdce1..5bb71815 100644 --- a/zork++/benches/benchmarks.rs +++ b/zork++/benches/benchmarks.rs @@ -20,7 +20,7 @@ pub fn build_project_benchmark(c: &mut Criterion) { let mut cache = ZorkCache::default(); c.bench_function("Generate commands", |b| { - b.iter(|| generate_commands(black_box(&program_data), black_box(&mut cache), &cli_args)) + b.iter(|| generate_commands(black_box(&program_data), black_box(&mut cache))) }); /* c.bench_function("Cache loading time", |b| { diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 7eee470e..0aa77f5b 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -53,8 +53,6 @@ pub fn load<'a>( .join(compiler.as_ref()) .with_extension(constants::CACHE_FILE_EXT); - // TODO: should we just have a cache dir with the __.json or similar? - // Or just ...//_.json let mut cache = if !cache_file_path.exists() { File::create(&cache_file_path).with_context(|| error_messages::FAILURE_LOADING_CACHE)?; helpers::initialize_cache(cache_path, cache_file_path, compiler, &output_dir)? @@ -510,7 +508,12 @@ mod helpers { for (target_name, target_data) in cache.generated_commands.targets.iter_mut() { let changes = remove_if_needed_from_cache_and_count_changes( &mut target_data.sources, - &program_data.targets.get(target_name).unwrap(/*TODO:*/).sources.sources, + &program_data + .targets + .get(target_name) + .unwrap() + .sources + .sources, ); if changes { return true; diff --git a/zork++/src/lib/cli/input/mod.rs b/zork++/src/lib/cli/input/mod.rs index cf7beadf..f4bf8091 100644 --- a/zork++/src/lib/cli/input/mod.rs +++ b/zork++/src/lib/cli/input/mod.rs @@ -10,14 +10,14 @@ use crate::project_model; /// use zork::cli::input::{CliArgs, Command, CppCompiler,TemplateValues}; /// /// let parser = CliArgs::parse_from( -/// ["", "-vv", "--match-files", "zork_linux.toml", "--root", ".", "--clear-cache", "--driver-path", "/usr/bin/clang-15/clang++", "test"] +/// ["", "-vv", "--match-files", "zork_linux.toml", "--root", ".", "--clear-cache", "--driver-path", "/usr/bin/clang-15/clang++", "--targets", "executable,tests", "test"] /// ); /// assert_eq!(parser.command, Command::Test); /// assert_eq!(parser.verbose, 2); /// assert_eq!(parser.root, Some(String::from("."))); /// assert_eq!(parser.clear_cache, true); /// assert_eq!(parser.driver_path, Some(String::from("/usr/bin/clang-15/clang++"))); -/// assert_eq!(parser.match_files, Some(String::from("zork_linux.toml"))); +/// assert_eq!(parser.targets, Some(vec![String::from("executable"), String::from("tests")])); /// // Create Template Project /// let parser = CliArgs::parse_from(["", "new", "example", "--git", "--compiler", "clang"]); @@ -48,6 +48,14 @@ pub struct CliArgs { #[arg(short, long, help = "Allows the user to specify the project's root")] pub root: Option, + #[arg( + short, + long, + help = "The name of the targets that Zork++ must take in consideration for the current invokation", + value_delimiter(',') + )] + pub targets: Option>, + #[arg( short, long, @@ -97,7 +105,6 @@ pub enum TemplateValues { } /// [`CppCompiler`] The C++ compilers available within Zork++ as a command line argument for the `new` argument -/// TODO: Possible future interesting on support the Intel's C++ compiler? #[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)] pub enum CppCompiler { CLANG, diff --git a/zork++/src/lib/cli/output/executors.rs b/zork++/src/lib/cli/output/executors.rs index 20bb615c..66adaccb 100644 --- a/zork++/src/lib/cli/output/executors.rs +++ b/zork++/src/lib/cli/output/executors.rs @@ -45,11 +45,14 @@ pub fn run_targets_generated_commands( ) -> Result<()> { log::info!("Proceeding to execute the generated commands..."); - // Process the user declared targets TODO: Filtered by cli? - for (target_name, target_data) in targets { + // Process the user declared targets + for (target_identifier, target_data) in targets + .iter_mut() + .filter(|(_, target_data)| target_data.enabled_for_current_program_iteration) + { log::info!( "Executing the generated commands of the sources declared for target: {:?}", - target_name + target_identifier.name() ); // Send to build to the compiler the sources declared for the current iteration target @@ -69,7 +72,7 @@ pub fn run_targets_generated_commands( log::info!( "Executing the linker command line for target: {:?}", - target_name + target_identifier.name() ); // Invoke the linker to generate the final product for the current iteration target helpers::execute_linker_command_line( @@ -95,7 +98,6 @@ pub fn autorun_generated_binary( let args = &[Argument::from( output_dir .join(compiler.as_ref()) - // TODO: join with the correct value .join(executable_name) .with_extension(constants::BINARY_EXTENSION), )]; diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index e942e4b1..d8f62065 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -16,7 +16,6 @@ use crate::project_model::target::TargetModel; use crate::utils::constants::error_messages; use crate::{ cache::ZorkCache, - cli::input::CliArgs, domain::translation_unit::{TranslationUnit, TranslationUnitKind}, project_model::{ compiler::{CppCompiler, StdLibMode}, @@ -33,11 +32,7 @@ pub mod data_factory; /// The core procedure. Generates the commands that will be sent to the compiler /// for every translation unit declared by the user for its project -pub fn generate_commands<'a>( - model: &'a ZorkModel<'a>, - cache: &mut ZorkCache<'a>, - cli_args: &'a CliArgs, -) -> Result<()> { +pub fn generate_commands<'a>(model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>) -> Result<()> { // Load the general args and the compiler specific ones if it's necessary load_flyweights_for_general_shared_data(model, cache); @@ -54,7 +49,7 @@ pub fn generate_commands<'a>( // Translation units and linker // Generate commands for the declared targets - process_targets(model, cache, cli_args)?; + process_targets(model, cache)?; Ok(()) } @@ -127,13 +122,12 @@ fn process_modules<'a>(model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>) -> R Ok(()) } -fn process_targets<'a>( - model: &'a ZorkModel<'a>, - cache: &mut ZorkCache<'a>, - _cli_args: &'a CliArgs, -) -> Result<()> { - for target in &model.targets { - // TODO: filter targets on cli input +fn process_targets<'a>(model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>) -> Result<()> { + for target in model + .targets + .iter() + .filter(|(_, target_data)| target_data.enabled_for_current_program_iteration) + { // 2nd - Generate the commands for the non-module sources generate_sources_cmds_args(model, cache, target)?; // 3rd - Generate the linker command for the 'target' declared by the user @@ -155,14 +149,14 @@ fn generate_sources_cmds_args<'a>( cache: &mut ZorkCache<'a>, target: (&'a TargetIdentifier<'a>, &'a TargetModel<'a>), ) -> Result<()> { + let target_identifier = target.0; + let target_data = target.1; + log::info!( "Generating the commands for the source files of target: {:?}", - &target.0 + target_identifier ); - let target_identifier = target.0; - let target_data = target.1; - process_kind_translation_units( model, cache, @@ -302,7 +296,7 @@ fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( /// Command line arguments generators procedures for C++ standard modules mod modules { - use std::path::Path; + use std::path::{Path, PathBuf}; use crate::cache::ZorkCache; use crate::compiler::helpers; @@ -460,10 +454,8 @@ mod modules { }; let cmd = SourceCommandLine { - directory: generated_bmi_path.clone(), // TODO: we are using the generated byproduct as the path for checking for its - // existence instead of the system header one while we don't implement the feature that inspects where the standard - // libraries lives. Isn't ideal, but serves the basic purpose of regenerating the commands when they it's needed, and - // system headers aren't unlikely to change (unless an stdlib update) + directory: PathBuf::default(), // NOTE: While we don't implement the lookup of the + // system headers filename: sys_module.to_string(), args, status: TranslationUnitStatus::PendingToBuild, diff --git a/zork++/src/lib/domain/target.rs b/zork++/src/lib/domain/target.rs index 463e3801..ffb03bcf 100644 --- a/zork++/src/lib/domain/target.rs +++ b/zork++/src/lib/domain/target.rs @@ -12,7 +12,6 @@ pub struct Target<'a> { pub sources: Vec>, pub linker: LinkerCommandLine<'a>, pub kind: TargetKind, - #[serde(skip)] pub enabled_for_current_program_iteration: bool, } diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index b29caea7..0cf1dd12 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -47,7 +47,7 @@ pub mod worker { // If this run is just for create a new C++ project with the given Zork++ projects creation // by template, create it and exit - if is_template_creation_then_create(cli_args, &abs_project_root)? { + if it_is_template_creation_then_create(cli_args, &abs_project_root)? { return Ok(()); }; @@ -66,21 +66,31 @@ pub mod worker { let config: ZorkConfigFile<'_> = config_file::zork_cfg_from_file(raw_file.as_str()) .with_context(|| error_messages::PARSE_CFG_FILE)?; - create_output_directory(&config, &abs_project_root)?; + create_output_directory(&config, &abs_project_root)?; // NOTE: review if the must + // rebuilt the cache and model if the + // output dir changes from + // previous let mut cache: ZorkCache<'_> = cache::load(&config, cli_args, &abs_project_root)?; - let program_data: ZorkModel<'_> = load_zork_model( - &mut cache, - &config_file, - config, - cli_args, - &abs_project_root, - )?; - map_model_targets_to_cache(&program_data, &mut cache); + let program_data = { + // The purpose of this scope is to let the reader to see clearly + // that the model will only be mutated within the scope of this + // block, and after it, it will be read-only data + let mut program_data: ZorkModel<'_> = load_zork_model( + &mut cache, + &config_file, + config, + cli_args, + &abs_project_root, + )?; + map_model_targets_to_cache(&mut program_data, &mut cache, cli_args)?; + + program_data + }; let generate_commands_ts = Instant::now(); - generate_commands(&program_data, &mut cache, cli_args) + generate_commands(&program_data, &mut cache) .with_context(|| error_messages::FAILURE_GENERATING_COMMANDS)?; log::debug!( @@ -89,16 +99,9 @@ pub mod worker { ); // Perform main work - let cfg_result = perform_main_work(cli_args, &program_data, &mut cache, cfg_path); - - // Now save the cache - cache.save(&program_data, cli_args)?; - - // Handle the errors after ensure that the cache is saved (if it didn't failed) - if cfg_result.is_err() { - log::error!("Failed to complete the job for: {:?}", cfg_path); - cfg_result? - } + perform_main_work(cli_args, &program_data, &mut cache, cfg_path)?; // NOTE: study if we + // must provide a flag to continue working with other cfgs (if present) if the current + // fails or abort without continuing (current behaviour) } Ok(()) @@ -106,7 +109,7 @@ pub mod worker { /// Inspects the [`CliArgs`] main passed argument, and if it's [`Command::New`] just creates a /// new *C++* project at the *abs_project_root* and exits - fn is_template_creation_then_create( + fn it_is_template_creation_then_create( cli_args: &CliArgs, abs_project_root: &Path, ) -> Result { @@ -129,13 +132,19 @@ pub mod worker { cache: &mut ZorkCache<'_>, cfg_path: &Path, ) -> Result<()> { - do_main_work_based_on_cli_input(cli_args, program_data, cache).with_context(|| { - format!( - "{}: {:?}", - error_messages::FAILED_BUILD_FOR_CFG_FILE, - cfg_path - ) - }) + let work_result = do_main_work_based_on_cli_input(cli_args, program_data, cache) + .with_context(|| { + format!( + "{}: {:?}", + error_messages::FAILED_BUILD_FOR_CFG_FILE, + cfg_path + ) + }); + + // Save the cached data for this config file + cache.save(program_data, cli_args)?; + + work_result.with_context(|| format!("Failed to complete the job for: {:?}", cfg_path)) } fn do_main_work_based_on_cli_input( @@ -171,41 +180,36 @@ pub mod worker { env_vars, )?; - match cli_args.command { - Command::Build => executors::run_targets_generated_commands( - program_data, - &general_args, - &compiler_specific_shared_args, - &mut generated_commands.targets, - &generated_commands.modules, - env_vars, - ), // TODO: group the duplicated calls - - Command::Run | Command::Test => { - let rgtc = executors::run_targets_generated_commands( - program_data, - &general_args, - &compiler_specific_shared_args, - &mut generated_commands.targets, - &generated_commands.modules, - env_vars, - ); + let target_executed_commands = executors::run_targets_generated_commands( + program_data, + &general_args, + &compiler_specific_shared_args, + &mut generated_commands.targets, + &generated_commands.modules, + env_vars, + ); - match rgtc { - Ok(_) => { - for target_name in generated_commands.targets.keys() { + match cli_args.command { + Command::Build => target_executed_commands, + Command::Run | Command::Test => match target_executed_commands { + Ok(_) => { + // NOTE: study if it's worth to use the same loop for building and + // autoexecuting, or otherwise, first build all, then autorun (actual + // behaviour) + for (target_identifier, target_data) in generated_commands.targets.iter() { + if target_data.enabled_for_current_program_iteration { executors::autorun_generated_binary( &program_data.compiler.cpp_compiler, &program_data.build.output_dir, - target_name.name(), + target_identifier.name(), )? } - - return Ok(()); } - Err(e) => Err(e), - }? - } + + return Ok(()); + } + Err(e) => Err(e), + }?, _ => todo!("{}", error_messages::CLI_ARGS_CMD_NEW_BRANCH), } } @@ -248,21 +252,65 @@ pub mod worker { /// [`ZorkConfigFile`] into the [`ZorkCache`], in order to avoid later calls with entry or insert /// which will be hidden on the code an harder to read for newcomers or after time without /// reading the codebase - fn map_model_targets_to_cache<'a>(program_data: &ZorkModel<'a>, cache: &mut ZorkCache<'a>) { - for (target_identifier, target_data) in program_data.targets.iter() { + fn map_model_targets_to_cache<'a>( + program_data: &mut ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + cli_args: &CliArgs, + ) -> Result<()> { + for (target_identifier, target_data) in program_data.targets.iter_mut() { + let target_name = target_identifier.name(); + // TODO: romper cada step en un único helper que puede ser unit tested + // 1st - Check if there's any new target to add to the tracked ones if !cache .generated_commands .targets .contains_key(target_identifier) { - log::debug!("Adding a new target to the cache: {:?}", target_identifier); + log::debug!("Adding a new target to the cache: {}", target_name); cache.generated_commands.targets.insert( target_identifier.clone(), Target::new_default_for_kind(target_data.kind), ); } + + // 2nd - Inspect the CliArgs to enable or disable targets for the current iteration + let cached_target = cache + .generated_commands + .targets + .get_mut(target_identifier) + .with_context(|| error_messages::TARGET_ENTRY_NOT_FOUND)?; + + if let Some(filtered_targets) = cli_args.targets.as_ref() { + // If there's Some(v), there's no need to check for emptyness on the underlying Vec + // (at least must be one) + let enabled = filtered_targets.iter().any(|t| t.eq(target_name)); + target_data.enabled_for_current_program_iteration = enabled; + // NOTE: we can perform the same check on the reader and rebuild the model if the + // cfg atrs changes via cli over iterations, avoiding having to mutate it here + log::info!( + "Target: {target_name} is {} from CLI for this iteration of Zork++", + if enabled { "enabled" } else { "disabled" } + ); + + cached_target.enabled_for_current_program_iteration = enabled; + // } + } else { + target_data.enabled_for_current_program_iteration = true; + cached_target.enabled_for_current_program_iteration = true; + } } + + log::warn!("TARGETS STATUS: {:?}", program_data.targets); + log::warn!( + "CACHE TARGETS STATUS: {:?}", + cache.generated_commands.targets + ); + + // 3rd - Remove from the cache the ones that the user removed from the cfg file (if they + // was tracked already) + + Ok(()) } /// Creates the directory for output the elements generated diff --git a/zork++/src/lib/project_model/mod.rs b/zork++/src/lib/project_model/mod.rs index ef4fd25a..e6ba242a 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -12,6 +12,7 @@ use color_eyre::Result; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; +use crate::utils::constants::error_messages; use crate::{cache::ZorkCache, domain::target::TargetIdentifier}; use crate::utils; @@ -33,11 +34,11 @@ pub struct ZorkModel<'a> { /// Loads the mapped [`ZorkModel`] for a concrete [`ZorkConfigFile`] from the [`ZorkCache`] pub fn load<'a>(cache: &ZorkCache<'a>) -> Result> { utils::fs::load_and_deserialize::(&cache.metadata.project_model_file_path) - .with_context(|| "Error loading the project model") + .with_context(|| error_messages::PROJECT_MODEL_LOAD) } /// Saves the mapped [`ZorkModel`] for a concrete [`ZorkConfigFile`] on the [`ZorkCache`] pub fn save(program_data: &ZorkModel, cache: &ZorkCache) -> Result<()> { utils::fs::save_file(&cache.metadata.project_model_file_path, program_data) - .with_context(|| "Error saving the project model") + .with_context(|| error_messages::PROJECT_MODEL_SAVE) } diff --git a/zork++/src/lib/project_model/target.rs b/zork++/src/lib/project_model/target.rs index fb2d4cfc..574ebd5b 100644 --- a/zork++/src/lib/project_model/target.rs +++ b/zork++/src/lib/project_model/target.rs @@ -10,4 +10,5 @@ pub struct TargetModel<'a> { pub sources: SourceSet<'a>, pub extra_args: Vec>, pub kind: TargetKind, + pub enabled_for_current_program_iteration: bool, } diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index 60d1637d..f88b671d 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -40,6 +40,7 @@ pub mod error_messages { pub const FAILURE_SAVING_CACHE: &str = "Error saving data to the Zork++ cache"; pub const GENERAL_ARGS_NOT_FOUND: &str = "Something went wrong loading the general arguments"; pub const PROJECT_MODEL_MAPPING: &str = "Error building the project model"; + pub const PROJECT_MODEL_LOAD: &str = "Error loading from the fs the project model"; pub const PROJECT_MODEL_SAVE: &str = "Error caching and saving to the fs the project model"; pub const TARGET_ENTRY_NOT_FOUND: &str = "Unlikely error happened while adding linkage data to a target"; diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 461703ab..52a665e0 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -350,6 +350,8 @@ fn assemble_target_model<'a>( sources, extra_args, kind: target_config.kind.unwrap_or_default(), + enabled_for_current_program_iteration: true, // NOTE: For now, it can only be manually + // disabled by cli args } } @@ -424,6 +426,7 @@ mod test { }, extra_args: vec!["-Werr".into()], kind: TargetKind::Executable, + enabled_for_current_program_iteration: true, }, ); targets.insert( @@ -439,6 +442,7 @@ mod test { }, extra_args: vec![], kind: TargetKind::Executable, + enabled_for_current_program_iteration: true, }, ); From af9de3b643d6773086e99b5a6891f789432562f8 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 4 Aug 2024 14:16:16 +0200 Subject: [PATCH 62/73] feat: Better Sourceset API --- zork++/src/lib/cache/mod.rs | 4 ++-- zork++/src/lib/compiler/mod.rs | 6 +++--- zork++/src/lib/project_model/sourceset.rs | 14 ++++++++---- zork++/src/lib/utils/constants.rs | 2 +- zork++/src/lib/utils/reader.rs | 26 ++++++++++------------- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 0aa77f5b..a42ff33f 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -508,12 +508,12 @@ mod helpers { for (target_name, target_data) in cache.generated_commands.targets.iter_mut() { let changes = remove_if_needed_from_cache_and_count_changes( &mut target_data.sources, - &program_data + program_data .targets .get(target_name) .unwrap() .sources - .sources, + .as_slice(), ); if changes { return true; diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index d8f62065..08657c2a 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -160,11 +160,11 @@ fn generate_sources_cmds_args<'a>( process_kind_translation_units( model, cache, - &target_data.sources.sources, // FIX: the sources.sources access with the repeated field + target_data.sources.as_slice(), // names TranslationUnitKind::SourceFile(target_identifier), ) - .with_context(|| error_messages::FAILURE_TARGET_SOURCES) + .with_context(|| error_messages::TARGET_SOURCES_FAILURE) } /// Generates the command line that will be passed to the linker to generate an target final product @@ -185,7 +185,7 @@ fn generate_linkage_targets_commands<'a>( .generated_commands .targets .get_mut(target_identifier) - .with_context(|| error_messages::FAILURE_TARGET_SOURCES)? + .with_context(|| error_messages::TARGET_SOURCES_FAILURE)? .linker; let compiler = &model.compiler.cpp_compiler; diff --git a/zork++/src/lib/project_model/sourceset.rs b/zork++/src/lib/project_model/sourceset.rs index 2e0c7c43..ce9bffc0 100644 --- a/zork++/src/lib/project_model/sourceset.rs +++ b/zork++/src/lib/project_model/sourceset.rs @@ -58,13 +58,19 @@ impl GlobPattern { } #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Clone)] -pub struct SourceSet<'a> { - pub sources: Vec>, -} +pub struct SourceSet<'a>(Vec>); impl<'a> SourceSet<'a> { + pub fn new(sources: Vec>) -> Self { + Self(sources) + } + + pub fn as_slice(&self) -> &[SourceFile<'a>] { + self.0.as_slice() + } + pub fn as_args_to(&self, dst: &mut Vec) -> Result<()> { - let args = self.sources.iter().map(|sf| sf.path()).map(Argument::from); + let args = self.0.iter().map(|sf| sf.path()).map(Argument::from); dst.extend(args); diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index f88b671d..9fc42142 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -58,7 +58,7 @@ pub mod error_messages { "An error happened while generating the commands for the module interfaces"; pub const FAILURE_MODULE_IMPLEMENTATIONS: &str = "An error happened while generating the commands for the module implementations"; - pub const FAILURE_TARGET_SOURCES: &str = + pub const TARGET_SOURCES_FAILURE: &str = "An error happened while generating the commands for the declared sources of the target"; pub const FAILURE_FINDING_TARGET: &str = "An error happened while retrieving the target information"; diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 52a665e0..81a10519 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -386,7 +386,7 @@ fn get_sources_for_target<'a>(srcs: Vec>, code_root: &Path) -> SourceSe }) .collect(); - SourceSet { sources } + SourceSet::new(sources) } #[cfg(test)] @@ -417,13 +417,11 @@ mod test { TargetIdentifier::from("executable"), TargetModel { output_name: "zork".into(), - sources: SourceSet { - sources: vec![SourceFile { - path: abs_path_for_mock.clone(), - file_stem: Cow::Borrowed("main"), - extension: Cow::Borrowed("cpp"), - }], - }, + sources: SourceSet::new(vec![SourceFile { + path: abs_path_for_mock.clone(), + file_stem: Cow::Borrowed("main"), + extension: Cow::Borrowed("cpp"), + }]), extra_args: vec!["-Werr".into()], kind: TargetKind::Executable, enabled_for_current_program_iteration: true, @@ -433,13 +431,11 @@ mod test { TargetIdentifier::from("tests"), TargetModel { output_name: "zork_tests".into(), - sources: SourceSet { - sources: vec![SourceFile { - path: abs_path_for_mock.clone(), - file_stem: Cow::Borrowed("tests_main"), - extension: Cow::Borrowed("cpp"), - }], - }, + sources: SourceSet::new(vec![SourceFile { + path: abs_path_for_mock.clone(), + file_stem: Cow::Borrowed("tests_main"), + extension: Cow::Borrowed("cpp"), + }]), extra_args: vec![], kind: TargetKind::Executable, enabled_for_current_program_iteration: true, From 5fc1646a8fac0f76591e57717b379887a970e5a5 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 4 Aug 2024 14:39:20 +0200 Subject: [PATCH 63/73] chore: doc-tests --- zork++/Cargo.lock | 117 ++++++++++-------- zork++/src/lib/config_file/build.rs | 6 +- zork++/src/lib/config_file/compiler.rs | 40 +++--- zork++/src/lib/config_file/executable.rs | 54 -------- zork++/src/lib/config_file/mod.rs | 15 +-- zork++/src/lib/config_file/modules.rs | 28 ++--- zork++/src/lib/config_file/project.rs | 10 +- zork++/src/lib/config_file/target.rs | 17 +-- zork++/src/lib/config_file/tests.rs | 51 -------- .../src/lib/domain/commands/command_lines.rs | 4 +- zork++/src/lib/lib.rs | 8 +- 11 files changed, 125 insertions(+), 225 deletions(-) delete mode 100644 zork++/src/lib/config_file/executable.rs delete mode 100644 zork++/src/lib/config_file/tests.rs diff --git a/zork++/Cargo.lock b/zork++/Cargo.lock index ae0a8770..ad829d0c 100644 --- a/zork++/Cargo.lock +++ b/zork++/Cargo.lock @@ -49,9 +49,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -64,36 +64,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -154,9 +154,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" +checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" [[package]] name = "cfg-if" @@ -220,9 +220,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.9" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", @@ -230,21 +230,21 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.9" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.1", + "clap_lex 0.7.2", "strsim", ] [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", @@ -263,9 +263,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "color-eyre" @@ -282,9 +282,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "core-foundation-sys" @@ -367,9 +367,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", @@ -377,9 +377,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", @@ -411,7 +411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -526,9 +526,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -543,9 +543,9 @@ checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -720,9 +720,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -763,7 +763,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -803,11 +803,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -820,9 +821,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.71" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -831,14 +832,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -915,9 +917,9 @@ checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf" [[package]] name = "typetag" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "661d18414ec032a49ece2d56eee03636e43c4e8d577047ab334c0ba892e29aaf" +checksum = "1f7ec175048b96728c30152928c52161bfcc8ea2bd3fb7ed4ccb7dec060b2834" dependencies = [ "erased-serde", "inventory", @@ -928,9 +930,9 @@ dependencies = [ [[package]] name = "typetag-impl" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac73887f47b9312552aa90ef477927ff014d63d1920ca8037c6c1951eab64bb1" +checksum = "84b5474fd169a5b02b6782b56bbbbff27e85947d4488e5501123687db3148647" dependencies = [ "proc-macro2", "quote", @@ -1041,11 +1043,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1072,6 +1074,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1141,12 +1152,12 @@ name = "zork" version = "0.9.0" dependencies = [ "chrono", - "clap 4.5.9", + "clap 4.5.13", "color-eyre", "criterion", "env_logger", "glob", - "indexmap 2.2.6", + "indexmap 2.3.0", "log", "regex", "serde", diff --git a/zork++/src/lib/config_file/build.rs b/zork++/src/lib/config_file/build.rs index 62b9f9c5..0e622ee5 100644 --- a/zork++/src/lib/config_file/build.rs +++ b/zork++/src/lib/config_file/build.rs @@ -6,9 +6,9 @@ use serde::*; /// [`BuildAttribute`] - Stores build process specific configuration /// /// * `output_dir` - An string representing a relative to the root path -/// where the compiler should dump the files generated in the build process. -/// If isn't specified, `Zork++` will generate an `./out/...` folder -/// by default +/// where the compiler should dump the files generated in the build process. +/// If isn't specified, `Zork++` will generate an `./out/...` folder +/// by default /// /// ```rust /// use zork::config_file::build::{BuildAttribute}; diff --git a/zork++/src/lib/config_file/compiler.rs b/zork++/src/lib/config_file/compiler.rs index c088ff90..2d920d08 100644 --- a/zork++/src/lib/config_file/compiler.rs +++ b/zork++/src/lib/config_file/compiler.rs @@ -9,30 +9,30 @@ use crate::project_model; /// targeting one of the available compilers within Zork++ /// /// * `cpp_compiler` - One of the available compilers within Zork++ -/// They are represented by an enumerated type named [`CppCompiler`], -/// that holds the different options where the user can choose +/// They are represented by an enumerated type named [`CppCompiler`], +/// that holds the different options where the user can choose /// /// * `driver_path` - The specific command line terminal identifier that will -/// call the compiler's binary. ie: clang++-15 will call a specific installation -/// of Clang in the host machine corresponding to the version 15 of the compiler. -/// This entry is particularly useful in Unix based OS or MinGW environments, -/// where multiple versions of the compiler lives at the same time, and their drivers -/// are identified by some sort of name like the one in the example of above +/// call the compiler's binary. ie: clang++-15 will call a specific installation +/// of Clang in the host machine corresponding to the version 15 of the compiler. +/// This entry is particularly useful in Unix based OS or MinGW environments, +/// where multiple versions of the compiler lives at the same time, and their drivers +/// are identified by some sort of name like the one in the example of above /// /// * `cpp_standard` - An string defining the version of the ISO -/// C++ standard that should be used on the compilation process +/// C++ standard that should be used on the compilation process /// /// * `std_lib` - The concrete C++ standard library (vendor specific) -/// to link the built code against +/// to link the built code against /// /// * `extra_args` - A comma separated list of strings that will be passed -/// to the generated command lines. This ones here will be placed in every -/// command line generated by Zork++. -/// For example, if *['-O3', '-Wall']* -/// are included here, this will be wired in the main command line (the executable), -/// the ones generated for compile modules (both interfaces and implementations) -/// and for the command line generated for build the specified test suite and -/// the test executable +/// to the generated command lines. This ones here will be placed in every +/// command line generated by Zork++. +/// For example, if *['-O3', '-Wall']* +/// are included here, this will be wired in the main command line (the executable), +/// the ones generated for compile modules (both interfaces and implementations) +/// and for the command line generated for build the specified test suite and +/// the test executable /// /// ### Tests /// @@ -60,10 +60,10 @@ use crate::project_model; /// ``` /// /// > Note: TOML table are toml commented (#) to allow us to parse -/// the inner attributes as the direct type that they belongs to. -/// That commented tables aren't the real TOML, they are just there -/// for testing and exemplification purposes of the inner attributes -/// of the configuration file. +/// > the inner attributes as the direct type that they belongs to. +/// > That commented tables aren't the real TOML, they are just there +/// > for testing and exemplification purposes of the inner attributes +/// > of the configuration file. /// /// For a test over a real example, please look at the /// [`zork::config_file::ZorkConfigFile`] doc-test diff --git a/zork++/src/lib/config_file/executable.rs b/zork++/src/lib/config_file/executable.rs deleted file mode 100644 index ce59b9c2..00000000 --- a/zork++/src/lib/config_file/executable.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Specify the execution configuration - -use serde::*; - -/// [`ExecutableAttribute`] - The core section to instruct the compiler to work with C++20 modules. -/// The most important are the base path to the interfaces and implementation files -/// * `executable_name`- The name that the final binary is to be generated with -/// * `sources_base_path` - Optional param for specify where the non-modules source files lives -/// * `sources` - The sources to be included in the project -/// * `extra_args` - Holds extra arguments that the user wants to introduce -/// in the build process -/// -/// ### Tests -/// -/// ```rust -/// use zork::config_file::executable::ExecutableAttribute; -/// const CONFIG_FILE_MOCK: &str = r#" -/// #[executable] -/// executable_name = "outputExecutableName" -/// sources_base_path = './src' -/// sources = [ -/// '*.cpp' -/// ] -/// extra_args = ['example'] -/// "#; -/// -/// let config: ExecutableAttribute = toml::from_str(CONFIG_FILE_MOCK) -/// .expect("A failure happened parsing the Zork toml file"); -/// -/// assert_eq!(config.executable_name, Some("outputExecutableName")); -/// assert_eq!(config.sources_base_path, Some("./src")); -/// assert_eq!(config.sources, Some(vec!["*.cpp"])); -/// assert_eq!(config.extra_args, Some(vec!["example"])) -/// ``` -/// > Note: TOML table are toml commented (#) to allow us to parse -/// the inner attributes as the direct type that they belongs to. -/// That commented tables aren't the real TOML, they are just there -/// for testing and exemplification purposes of the inner attributes -/// of the configuration file. -/// -/// For a test over a real example, please look at the -/// [`zork::config_file::ZorkConfigFile`] doc-test -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] -#[serde(deny_unknown_fields)] -pub struct ExecutableAttribute<'a> { - #[serde(borrow)] - pub executable_name: Option<&'a str>, - #[serde(borrow)] - pub sources_base_path: Option<&'a str>, - #[serde(borrow)] - pub sources: Option>, - #[serde(borrow)] - pub extra_args: Option>, -} diff --git a/zork++/src/lib/config_file/mod.rs b/zork++/src/lib/config_file/mod.rs index d32f0fb2..b44d0639 100644 --- a/zork++/src/lib/config_file/mod.rs +++ b/zork++/src/lib/config_file/mod.rs @@ -2,19 +2,16 @@ //! parsed data lives. pub mod build; pub mod compiler; -pub mod executable; pub mod modules; pub mod project; pub mod target; -pub mod tests; use indexmap::IndexMap; use serde::{Deserialize, Deserializer, Serialize}; use self::{ - build::BuildAttribute, compiler::CompilerAttribute, executable::ExecutableAttribute, - modules::ModulesAttribute, project::ProjectAttribute, target::TargetAttribute, - tests::TestsAttribute, + build::BuildAttribute, compiler::CompilerAttribute, modules::ModulesAttribute, + project::ProjectAttribute, target::TargetAttribute, }; /// ```rust @@ -24,7 +21,7 @@ use self::{ /// target::TargetAttribute /// }; /// use zork::domain::target::TargetKind; -/// use std::collections::HashMap; +/// use indexmap::IndexMap; /// /// const CONFIG_FILE_MOCK: &str = r#" /// [project] @@ -56,7 +53,7 @@ use self::{ /// assert_eq!(compiler_attribute.cpp_compiler, CppCompiler::CLANG); /// assert_eq!(compiler_attribute.cpp_standard, LanguageLevel::CPP20); /// -/// let targets: &HashMap<&str, TargetAttribute<'_>> = &config.targets; +/// let targets: &IndexMap<&str, TargetAttribute<'_>> = &config.targets; /// assert!(!targets.is_empty()); /// /// let executable_target: &TargetAttribute<'_> = targets.get("executable").expect("Target named @@ -93,10 +90,6 @@ pub struct ZorkConfigFile<'a> { pub modules: Option>, #[serde(deserialize_with = "deserialize_targets")] pub targets: IndexMap<&'a str, TargetAttribute<'a>>, - #[serde(borrow)] - pub executable: Option>, - #[serde(borrow)] - pub tests: Option>, } fn deserialize_targets<'de, D>( diff --git a/zork++/src/lib/config_file/modules.rs b/zork++/src/lib/config_file/modules.rs index a8df0038..2400a55a 100644 --- a/zork++/src/lib/config_file/modules.rs +++ b/zork++/src/lib/config_file/modules.rs @@ -2,13 +2,13 @@ use serde::{Deserialize, Serialize}; -/// [`ModulesAttribute`] - The core section to instruct the compiler to work with C++20 modules. The most important are the base path to the interfaces and implementation files +/// [`ModulesAttribute`] - The core section to instruct the compiler to work with C++20 modules. /// * `base_ifcs_dir`- Base directory to shortcut the path of the implementation files /// * `interfaces` - A list to define the module interface translation units for the project /// * `base_impls_dir` - Base directory to shortcut the path of the implementation files /// * `implementations` - A list to define the module interface translation units for the project /// * `sys_modules` - An array field explicitly declare which system headers -/// must be precompiled in order to make the importable translation units +/// must be precompiled in order to make the importable translation units /// /// ### Tests /// @@ -78,16 +78,16 @@ pub struct ModulesAttribute<'a> { /// * `file`- The path of a primary module interface (relative to base_ifcs_path if applies) /// /// * `module_name` - An optional field for make an explicit declaration of the -/// C++ module declared on this module interface with the `export module 'module_name' -/// statement. If this attribute isn't present, Zork++ will assume that the -/// C++ module declared within this file is equals to the filename +/// C++ module declared on this module interface with the `export module 'module_name' +/// statement. If this attribute isn't present, Zork++ will assume that the +/// C++ module declared within this file is equals to the filename /// /// * `partition` - Whenever this attribute is present, we are telling Zork++ that the -/// actual translation unit is a partition, either an interface partition or an implementation -/// partition unit +/// actual translation unit is a partition, either an interface partition or an implementation +/// partition unit /// /// * `dependencies` - An optional array field for declare the module interfaces -/// in which this file is dependent on +/// in which this file is dependent on /// ### Tests /// ```rust /// use zork::config_file::modules::ModulesAttribute; @@ -148,13 +148,13 @@ pub struct ModuleInterface<'a> { /// * `module`- The interface module unit that this partitions belongs to /// /// * `partition_name` - An optional field for explicitly declare the name of a module interface -/// partition, or a module implementation partition. -/// Currently this requirement is only needed if your partitions file names aren't -/// declared as the modules convention, that is `module_name-partition_name.extension` +/// partition, or a module implementation partition. +/// Currently this requirement is only needed if your partitions file names aren't +/// declared as the modules convention, that is `module_name-partition_name.extension` /// /// * `is_internal_partition` - Optional field for declare that the module is actually -/// a module for hold implementation details, known as module implementation partitions. -/// This option only takes effect with MSVC +/// a module for hold implementation details, known as module implementation partitions. +/// This option only takes effect with MSVC #[derive(Serialize, Deserialize, Debug, Default, PartialEq)] pub struct ModulePartition<'a> { #[serde(borrow)] @@ -169,7 +169,7 @@ pub struct ModulePartition<'a> { /// /// * `file`- The path of a primary module interface (relative to base_ifcs_path) /// * `dependencies` - An optional array field for declare the module interfaces -/// in which this file is dependent on +/// in which this file is dependent on /// /// ### Tests /// ```rust diff --git a/zork++/src/lib/config_file/project.rs b/zork++/src/lib/config_file/project.rs index 5450a2f9..470152be 100644 --- a/zork++/src/lib/config_file/project.rs +++ b/zork++/src/lib/config_file/project.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; /// [`ProjectAttribute`] - Metadata about the user's project /// * `name` - The C++ project's name /// * `authors` - A comma separated list of strings indicating the -/// authors that are responsible for the project +/// authors that are responsible for the project /// /// ### Tests /// @@ -29,10 +29,10 @@ use serde::{Deserialize, Serialize}; /// ``` /// /// > Note: TOML table are toml commented (#) to allow us to parse -/// the inner attributes as the direct type that they belongs to. -/// That commented tables aren't the real TOML, they are just there -/// for testing and exemplification purposes of the inner attributes -/// of the configuration file. +/// > the inner attributes as the direct type that they belongs to. +/// > That commented tables aren't the real TOML, they are just there +/// > for testing and exemplification purposes of the inner attributes +/// > of the configuration file. /// /// For a test over a real example, please look at the /// [`zork::config_file::ZorkConfigFile`] doc-test diff --git a/zork++/src/lib/config_file/target.rs b/zork++/src/lib/config_file/target.rs index 9ed1485c..5a7e3b89 100644 --- a/zork++/src/lib/config_file/target.rs +++ b/zork++/src/lib/config_file/target.rs @@ -15,6 +15,7 @@ use crate::domain::target::TargetKind; /// /// ```rust /// use zork::config_file::target::TargetAttribute; +/// use zork::domain::target::TargetKind; /// const CONFIG_FILE_MOCK: &str = r#" /// #[target.executable] /// output_name = "some_executable" @@ -26,16 +27,16 @@ use crate::domain::target::TargetKind; /// let config: TargetAttribute = toml::from_str(CONFIG_FILE_MOCK) /// .expect("A failure happened parsing the Zork toml file"); /// -/// assert_eq!(config.executable_name, Some("some_executable")); -/// assert_eq!(config.sources, Some(vec!["*.cpp"])); -/// assert_eq!(config.extra_args, Some(vec!["-Wall"])) -/// assert_eq!(config.kind, TargetKind::Executable) +/// assert_eq!(config.output_name, Some("some_executable")); +/// assert_eq!(config.sources, vec!["*.cpp"]); +/// assert_eq!(config.extra_args, Some(vec!["-Wall"])); +/// assert_eq!(config.kind, Some(TargetKind::Executable)); /// ``` /// > Note: TOML table are toml commented (#) to allow us to parse -/// the inner attributes as the direct type that they belongs to. -/// That commented tables aren't the real TOML, they are just there -/// for testing and exemplification purposes of the inner attributes -/// of the configuration file. +/// > the inner attributes as the direct type that they belongs to. +/// > That commented tables aren't the real TOML, they are just there +/// > for testing and exemplification purposes of the inner attributes +/// > of the configuration file. /// /// For a test over a real example, please look at the /// [`zork::config_file::ZorkConfigFile`] doc-test diff --git a/zork++/src/lib/config_file/tests.rs b/zork++/src/lib/config_file/tests.rs deleted file mode 100644 index a41cf26f..00000000 --- a/zork++/src/lib/config_file/tests.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Tests attribute allows you to run the tests written for your application in a convenient way -use serde::*; - -/// [`TestsAttribute`] - Tests attribute allows you to run the tests written for your application in a convenient way -/// * `tests_executable_name` - The name of the generated binary -/// * `sources_base_path` - Base common path for the source files -/// * `sources` - All the source files that must be included -/// * `extra_args`- Extra arguments to add to the generated command line(s) -/// -/// ### Tests -/// -/// ```rust -/// use zork::config_file::tests::TestsAttribute; -/// -/// const CONFIG_FILE_MOCK: &str = r#" -/// #[tests] -/// test_executable_name = 'Zork++ tests' -/// sources_base_path = 'path_test' -/// sources = [ '*.cpp' ] -/// extra_args = ['extra_argument to run test'] -///"#; -/// -/// let config: TestsAttribute = toml::from_str(CONFIG_FILE_MOCK) -/// .expect("A failure happened parsing the Zork toml file"); -/// -/// assert_eq!(config.test_executable_name, Some("Zork++ tests")); -/// assert_eq!(config.sources_base_path, Some("path_test")); -/// assert_eq!(config.sources, Some(vec!["*.cpp"])); -/// assert_eq!(config.extra_args, Some(vec!["extra_argument to run test"])); -/// -/// ``` -/// -/// > Note: TOML table are toml commented (#) to allow us to parse -/// the inner attributes as the direct type that they belongs to. -/// That commented tables aren't the real TOML, they are just there -/// for testing and exemplification purposes of the inner attributes -/// of the configuration file. -/// -/// For a test over a real example, please look at the -/// [`zork::config_file::ZorkConfigFile`] doc-test -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct TestsAttribute<'a> { - #[serde(borrow)] - pub test_executable_name: Option<&'a str>, - #[serde(borrow)] - pub sources_base_path: Option<&'a str>, - #[serde(borrow)] - pub sources: Option>, - #[serde(borrow)] - pub extra_args: Option>, -} diff --git a/zork++/src/lib/domain/commands/command_lines.rs b/zork++/src/lib/domain/commands/command_lines.rs index 22d5007a..ca37b8b0 100644 --- a/zork++/src/lib/domain/commands/command_lines.rs +++ b/zork++/src/lib/domain/commands/command_lines.rs @@ -33,8 +33,8 @@ pub struct ModulesCommands<'a> { /// * filename*: the translation unit declared name on the fs with the extension /// * args*: member that holds all the cmd arguments that will be passed to the compiler driver /// * status*: A [`TranslationUnitStatus`] that represents all the different phases that a source command -/// line can have among all the different iterations of the program, changing according to the modifications -/// over the translation unit in the fs and the result of the build execution +/// line can have among all the different iterations of the program, changing according to the modifications +/// over the translation unit in the fs and the result of the build execution /// *byproduct*: A [`PathBuf`] like [`Argument`] which hold the physical address on the filesystem /// where the compiled object file will be dumped after building it #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 0cf1dd12..134e17af 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -323,12 +323,12 @@ pub mod worker { /// /// Under /zork, some new folders are created: /// - a /intrinsics folder in created as well, - /// where different specific details of Zork++ are stored - /// related with the C++ compilers + /// where different specific details of Zork++ are stored + /// related with the C++ compilers /// /// - a /cache folder, where lives the metadata cached by Zork++ - /// in order to track different aspects of the program (last time - /// modified files, last process build time...) + /// in order to track different aspects of the program (last time + /// modified files, last process build time...) fn create_output_directory(config: &ZorkConfigFile, project_root: &Path) -> Result<()> { let compiler: CppCompiler = config.compiler.cpp_compiler.into(); let compiler_name = compiler.as_ref(); From 59c3c45df4c289a7d5584d9b674f49fbf4c6c8fb Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 4 Aug 2024 14:45:07 +0200 Subject: [PATCH 64/73] chore: doc-lazy from clippy warnings --- zork++/src/lib/domain/commands/command_lines.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zork++/src/lib/domain/commands/command_lines.rs b/zork++/src/lib/domain/commands/command_lines.rs index ca37b8b0..c58d9e50 100644 --- a/zork++/src/lib/domain/commands/command_lines.rs +++ b/zork++/src/lib/domain/commands/command_lines.rs @@ -35,8 +35,9 @@ pub struct ModulesCommands<'a> { /// * status*: A [`TranslationUnitStatus`] that represents all the different phases that a source command /// line can have among all the different iterations of the program, changing according to the modifications /// over the translation unit in the fs and the result of the build execution +/// /// *byproduct*: A [`PathBuf`] like [`Argument`] which hold the physical address on the filesystem -/// where the compiled object file will be dumped after building it +/// where the compiled object file will be dumped after building it #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct SourceCommandLine<'a> { pub directory: PathBuf, From f45886fd670e0961aa91c06860e007c15d416421 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 4 Aug 2024 21:44:06 +0200 Subject: [PATCH 65/73] fix: Removing the sysmodules byproducts from the GCC linker command line of every target, since GCC handles the references to the modules via gcm.cache --- zork++/src/lib/cli/output/executors.rs | 17 +++++++++++------ zork++/src/lib/lib.rs | 8 +------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/zork++/src/lib/cli/output/executors.rs b/zork++/src/lib/cli/output/executors.rs index 66adaccb..37c8daef 100644 --- a/zork++/src/lib/cli/output/executors.rs +++ b/zork++/src/lib/cli/output/executors.rs @@ -202,9 +202,8 @@ mod helpers { env_vars: &EnvVars, target_data: &mut Target, ) -> Result { - let linker_args = target_data - .linker - .get_target_output_for(program_data.compiler.cpp_compiler); + let compiler = program_data.compiler.cpp_compiler; + let linker_args = target_data.linker.get_target_output_for(compiler); let linker_sources_byproducts = target_data.sources.iter().map(|scl| &scl.byproduct); let modules_byproducts = modules @@ -213,16 +212,22 @@ mod helpers { .iter() .chain(modules.c_compat_stdlib.iter()) .chain(modules.interfaces.iter()) - .chain(modules.implementations.iter()) - .chain(modules.system_modules.iter()) + .chain(if compiler.ne(&CppCompiler::GCC) { + modules.system_modules.iter() + } else { + [].iter() + }) + // NOTE: The embedeed if above allows us to avoid to clone iterators by reasining data + // if the compiler is GCC, where we don't want to chain the system modules since GCC + // already handles their compilation products itself (gcm.cache) .map(|scl| &scl.byproduct); let args = general_args .iter() .chain(linker_args.iter()) .chain(compiler_specific_shared_args.iter()) - .chain(linker_sources_byproducts) .chain(modules_byproducts) + .chain(linker_sources_byproducts) .collect::(); let r = execute_command(program_data, &args, env_vars); diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 134e17af..e50173f1 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -301,13 +301,7 @@ pub mod worker { } } - log::warn!("TARGETS STATUS: {:?}", program_data.targets); - log::warn!( - "CACHE TARGETS STATUS: {:?}", - cache.generated_commands.targets - ); - - // 3rd - Remove from the cache the ones that the user removed from the cfg file (if they + // TODO: 3rd - Remove from the cache the ones that the user removed from the cfg file (if they // was tracked already) Ok(()) From 80e1752fd0eca9894ce239d02165b8b220b16159 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 4 Aug 2024 22:07:48 +0200 Subject: [PATCH 66/73] fix: bringing the chain of the modules.implementations for the linker command line, deleted by mistake (too much v-dd) --- zork++/src/lib/cli/output/executors.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zork++/src/lib/cli/output/executors.rs b/zork++/src/lib/cli/output/executors.rs index 37c8daef..47bf2920 100644 --- a/zork++/src/lib/cli/output/executors.rs +++ b/zork++/src/lib/cli/output/executors.rs @@ -212,6 +212,7 @@ mod helpers { .iter() .chain(modules.c_compat_stdlib.iter()) .chain(modules.interfaces.iter()) + .chain(modules.implementations.iter()) .chain(if compiler.ne(&CppCompiler::GCC) { modules.system_modules.iter() } else { From 14dbc8e3acf250bfcd0a3ae89aea21be98b9a368 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 5 Aug 2024 19:13:45 +0200 Subject: [PATCH 67/73] feat: New type for better readability while passing the shared args and env vars to the commands executors --- zork++/src/lib/cli/output/executors.rs | 94 +++++++++++++------------ zork++/src/lib/compiler/clang.rs | 0 zork++/src/lib/compiler/data_factory.rs | 4 -- zork++/src/lib/compiler/mod.rs | 25 +++---- zork++/src/lib/domain/flyweight_data.rs | 52 ++++++++++++++ zork++/src/lib/domain/mod.rs | 1 + zork++/src/lib/lib.rs | 76 +++++++++----------- 7 files changed, 148 insertions(+), 104 deletions(-) delete mode 100644 zork++/src/lib/compiler/clang.rs create mode 100644 zork++/src/lib/domain/flyweight_data.rs diff --git a/zork++/src/lib/cli/output/executors.rs b/zork++/src/lib/cli/output/executors.rs index 47bf2920..95391dbd 100644 --- a/zork++/src/lib/cli/output/executors.rs +++ b/zork++/src/lib/cli/output/executors.rs @@ -5,43 +5,36 @@ use std::ffi::OsStr; use std::{path::Path, process::ExitStatus}; use crate::cache::EnvVars; -use crate::domain::commands::arguments::{Argument, Arguments}; +use crate::domain::commands::arguments::Argument; use crate::domain::commands::command_lines::ModulesCommands; +use crate::domain::flyweight_data::FlyweightData; use crate::domain::target::{Target, TargetIdentifier}; use crate::domain::translation_unit::TranslationUnitStatus; +use crate::utils::constants::error_messages; use crate::{ project_model::{compiler::CppCompiler, ZorkModel}, utils::constants, }; +use color_eyre::eyre::ContextCompat; use color_eyre::{eyre::Context, Report, Result}; use indexmap::IndexMap; pub fn run_modules_generated_commands( program_data: &ZorkModel<'_>, - general_args: &Arguments<'_>, - compiler_specific_shared_args: &Arguments, + flyweight_data: &FlyweightData, modules_generated_commands: &mut ModulesCommands<'_>, - env_vars: &EnvVars, ) -> Result<()> { log::info!("Proceeding to execute the generated modules commands..."); // Process the modules - helpers::process_modules_commands( - program_data, - general_args, - compiler_specific_shared_args, - modules_generated_commands, - env_vars, - ) + helpers::process_modules_commands(program_data, flyweight_data, modules_generated_commands) } pub fn run_targets_generated_commands( program_data: &ZorkModel<'_>, - general_args: &Arguments<'_>, - compiler_specific_shared_args: &Arguments, + flyweight_data: &FlyweightData, targets: &mut IndexMap, modules: &ModulesCommands<'_>, - env_vars: &EnvVars, ) -> Result<()> { log::info!("Proceeding to execute the generated commands..."); @@ -50,35 +43,54 @@ pub fn run_targets_generated_commands( .iter_mut() .filter(|(_, target_data)| target_data.enabled_for_current_program_iteration) { + let env_vars = flyweight_data.env_vars; + log::info!( "Executing the generated commands of the sources declared for target: {:?}", target_identifier.name() ); + let extra_args = &program_data + .targets + .get(target_identifier) + .with_context(|| error_messages::TARGET_ENTRY_NOT_FOUND)? + .extra_args; + + let compile_but_dont_link: [Argument; 1] = + [Argument::from(match program_data.compiler.cpp_compiler { + CppCompiler::CLANG | CppCompiler::GCC => "-c", + CppCompiler::MSVC => "/c", + })]; + + let shared_args = flyweight_data + .general_args + .iter() + .chain(flyweight_data.shared_args.iter()) + .chain(extra_args.as_slice()) + .chain(compile_but_dont_link.iter()) + .collect(); + // Send to build to the compiler the sources declared for the current iteration target for source in target_data .sources .iter_mut() .filter(|scl| scl.status.eq(&TranslationUnitStatus::PendingToBuild)) { - helpers::execute_source_command_line( - program_data, - general_args, - compiler_specific_shared_args, - env_vars, - source, - )?; + helpers::execute_source_command_line(program_data, &shared_args, env_vars, source)?; } log::info!( "Executing the linker command line for target: {:?}", target_identifier.name() ); + // Invoke the linker to generate the final product for the current iteration target + let (_compile_but_dont_link, linker_shared_args) = shared_args.split_last() + .expect("Unlikely error happened while removing the compile but don't link flag from the flyweight data. This is a BUG, so please, open an issue on upsteam"); + helpers::execute_linker_command_line( program_data, - general_args, - compiler_specific_shared_args, + linker_shared_args, modules, env_vars, target_data, @@ -149,6 +161,7 @@ mod helpers { use crate::cli::output::executors::execute_command; use crate::domain::commands::arguments::{Argument, Arguments}; use crate::domain::commands::command_lines::{ModulesCommands, SourceCommandLine}; + use crate::domain::flyweight_data::FlyweightData; use crate::domain::target::Target; use crate::domain::translation_unit::TranslationUnitStatus; use crate::project_model::compiler::CppCompiler; @@ -160,22 +173,14 @@ mod helpers { pub(crate) fn execute_source_command_line( program_data: &ZorkModel<'_>, - general_args: &Arguments<'_>, - compiler_specific_shared_args: &Arguments<'_>, + shared_args: &Arguments<'_>, env_vars: &HashMap, source: &mut SourceCommandLine<'_>, ) -> Result<()> { - let compile_but_dont_link: [Argument; 1] = - [Argument::from(match program_data.compiler.cpp_compiler { - CppCompiler::CLANG | CppCompiler::GCC => "-c", - CppCompiler::MSVC => "/c", - })]; - - let args = general_args + let args = shared_args + .as_slice() .iter() - .chain(compiler_specific_shared_args.iter()) .chain(source.args.as_slice().iter()) - .chain(compile_but_dont_link.iter()) .collect::(); let r = execute_command(program_data, &args, env_vars); @@ -196,8 +201,7 @@ mod helpers { pub(crate) fn execute_linker_command_line( program_data: &ZorkModel, - general_args: &Arguments, - compiler_specific_shared_args: &Arguments, + shared_args: &[Argument], modules: &ModulesCommands<'_>, env_vars: &EnvVars, target_data: &mut Target, @@ -223,10 +227,9 @@ mod helpers { // already handles their compilation products itself (gcm.cache) .map(|scl| &scl.byproduct); - let args = general_args + let args = shared_args .iter() .chain(linker_args.iter()) - .chain(compiler_specific_shared_args.iter()) .chain(modules_byproducts) .chain(linker_sources_byproducts) .collect::(); @@ -247,10 +250,8 @@ mod helpers { pub(crate) fn process_modules_commands( program_data: &ZorkModel<'_>, - general_args: &Arguments, - compiler_specific_shared_args: &Arguments, + flyweight_data: &FlyweightData, generated_commands: &mut ModulesCommands<'_>, - env_vars: &std::collections::HashMap, ) -> Result<()> { let translation_units_commands: Vec<&mut SourceCommandLine> = get_modules_translation_units_commands( @@ -275,14 +276,19 @@ mod helpers { for translation_unit_cmd in translation_units_commands { // Join the concrete args of any translation unit with the ones held in the flyweights - let translation_unit_cmd_args = general_args + let translation_unit_cmd_args = flyweight_data + .general_args .iter() - .chain(compiler_specific_shared_args.iter()) + .chain(flyweight_data.shared_args.iter()) .chain(&compile_but_dont_link) .chain(translation_unit_cmd.args.iter()) .collect::(); - let r = execute_command(program_data, &translation_unit_cmd_args, env_vars); + let r = execute_command( + program_data, + &translation_unit_cmd_args, + flyweight_data.env_vars, + ); translation_unit_cmd.status = TranslationUnitStatus::from(&r); if let Err(e) = r { diff --git a/zork++/src/lib/compiler/clang.rs b/zork++/src/lib/compiler/clang.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/zork++/src/lib/compiler/data_factory.rs b/zork++/src/lib/compiler/data_factory.rs index c75e3ca8..84e9718d 100644 --- a/zork++/src/lib/compiler/data_factory.rs +++ b/zork++/src/lib/compiler/data_factory.rs @@ -85,7 +85,6 @@ impl CompilerCommonArguments for ClangCommonArgs { args.push(self.std_lib.as_arg()); args.push(&self.implicit_modules); args.push(&self.implicit_module_map); - args.push(&self.prebuilt_module_path); args } } @@ -125,18 +124,15 @@ pub struct ClangCommonArgs { std_lib: StdLib, implicit_modules: Cow<'static, str>, implicit_module_map: Cow<'static, str>, - prebuilt_module_path: String, } impl ClangCommonArgs { pub fn new(model: &ZorkModel<'_>) -> Self { - let compiler = model.compiler.cpp_compiler; let out_dir: &Path = model.build.output_dir.as_ref(); Self { std_lib: model.compiler.std_lib.unwrap_or_default(), implicit_modules: "-fimplicit-modules".into(), implicit_module_map: clang_args::implicit_module_map(out_dir), - prebuilt_module_path: clang_args::add_prebuilt_module_path(compiler, out_dir), } } } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 08657c2a..5d8b0e99 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -30,9 +30,9 @@ use self::data_factory::CommonArgs; pub mod data_factory; -/// The core procedure. Generates the commands that will be sent to the compiler +/// The core procedure. Generates the commands arguments that will be sent to the compiler /// for every translation unit declared by the user for its project -pub fn generate_commands<'a>(model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>) -> Result<()> { +pub fn generate_commands_arguments<'a>(model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>) -> Result<()> { // Load the general args and the compiler specific ones if it's necessary load_flyweights_for_general_shared_data(model, cache); @@ -329,6 +329,7 @@ mod modules { arguments.push("-x"); arguments.push("c++-module"); arguments.push("--precompile"); + arguments.push(clang_args::add_prebuilt_module_path(compiler, out_dir)); clang_args::add_direct_module_interfaces_dependencies( &interface.dependencies, compiler, @@ -399,6 +400,7 @@ mod modules { arguments.push("-o"); arguments.push(&obj_file_path); + arguments.push(clang_args::add_prebuilt_module_path(compiler, out_dir)); clang_args::add_direct_module_interfaces_dependencies( &implementation.dependencies, compiler, @@ -492,12 +494,11 @@ mod modules { /// Specific operations over source files mod sources { use crate::cache::ZorkCache; - use crate::domain::commands::arguments::Arguments; + use crate::domain::commands::arguments::{clang_args, Arguments}; use crate::domain::commands::command_lines::SourceCommandLine; use crate::domain::target::TargetIdentifier; use crate::domain::translation_unit::TranslationUnit; use crate::project_model::sourceset::SourceFile; - use crate::project_model::target::TargetModel; use crate::project_model::{compiler::CppCompiler, ZorkModel}; use crate::utils::constants::error_messages; use color_eyre::eyre::{ContextCompat, Result}; @@ -513,13 +514,15 @@ mod sources { ) -> Result<()> { let compiler = model.compiler.cpp_compiler; let out_dir = model.build.output_dir.as_ref(); - let target: &TargetModel<'_> = model - .targets - .get(target_identifier) - .with_context(|| error_messages::FAILURE_FINDING_TARGET)?; let mut arguments = Arguments::default(); - arguments.extend_from_to_argument_slice(&target.extra_args); + + if compiler.eq(&CppCompiler::CLANG) { + arguments.push(clang_args::add_prebuilt_module_path(compiler, out_dir)); + } + + // arguments.extend_from_to_argument_slice(&target.extra_args); // TODO: add them as flyweight + // data on the executors let obj_file = helpers::generate_obj_file(compiler, out_dir, source); let fo = if compiler.eq(&CppCompiler::MSVC) { @@ -549,9 +552,7 @@ mod sources { } } -/// Helpers for reduce the cyclomatic complexity introduced by the -/// kind of workflow that should be done with this parse, format and -/// generate. +/// Helpers for reduce the cyclomatic complexity mod helpers { use super::*; use crate::domain::commands::command_lines::SourceCommandLine; diff --git a/zork++/src/lib/domain/flyweight_data.rs b/zork++/src/lib/domain/flyweight_data.rs new file mode 100644 index 00000000..fbde7d57 --- /dev/null +++ b/zork++/src/lib/domain/flyweight_data.rs @@ -0,0 +1,52 @@ +use color_eyre::eyre::ContextCompat; +use color_eyre::eyre::Result; + +use super::commands::arguments::Arguments; +use crate::cache::CompilersMetadata; +use crate::compiler::data_factory::CommonArgs; +use crate::compiler::data_factory::CompilerCommonArguments; +use crate::{ + cache::EnvVars, + project_model::{compiler::CppCompiler, ZorkModel}, + utils::constants::error_messages, +}; + +/// Convenient datastructure to hold the common args for all the [`super::commands::command_lines::SourceCommandLine`] +/// once they are initialized and stored on the cache, so we just move them once (into this type) +/// and we can pass the struct around to the executors +pub struct FlyweightData<'a> { + pub general_args: Arguments<'a>, + pub shared_args: Arguments<'a>, + pub env_vars: &'a EnvVars, +} + +impl<'a> FlyweightData<'a> { + pub fn new( + program_data: &ZorkModel, + general_args: &'a mut Option>, + compiler_common_args: &'a mut Option>, + compilers_metadata: &'a CompilersMetadata, + ) -> Result { + let general_args = general_args + .as_mut() + .with_context(|| error_messages::GENERAL_ARGS_NOT_FOUND)? + .get_args(); + + let shared_args = compiler_common_args + .as_mut() + .with_context(|| error_messages::COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND)? + .get_args(); + + let env_vars = match program_data.compiler.cpp_compiler { + CppCompiler::MSVC => &compilers_metadata.msvc.env_vars, + CppCompiler::CLANG => &compilers_metadata.clang.env_vars, + CppCompiler::GCC => &compilers_metadata.gcc.env_vars, + }; + + Ok(Self { + general_args, + shared_args, + env_vars, + }) + } +} diff --git a/zork++/src/lib/domain/mod.rs b/zork++/src/lib/domain/mod.rs index e53f399c..788b8e7d 100644 --- a/zork++/src/lib/domain/mod.rs +++ b/zork++/src/lib/domain/mod.rs @@ -1,3 +1,4 @@ pub mod commands; +pub mod flyweight_data; pub mod target; pub mod translation_unit; diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index e50173f1..047c7c3d 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -17,6 +17,7 @@ pub mod utils; pub mod worker { use crate::config_file; use crate::config_file::ZorkConfigFile; + use crate::domain::flyweight_data::FlyweightData; use crate::domain::target::Target; use crate::project_model; use std::path::PathBuf; @@ -29,7 +30,7 @@ pub mod worker { input::{CliArgs, Command}, output::executors, }, - compiler::generate_commands, + compiler::generate_commands_arguments, project_model::{compiler::CppCompiler, ZorkModel}, utils::{ self, @@ -66,7 +67,7 @@ pub mod worker { let config: ZorkConfigFile<'_> = config_file::zork_cfg_from_file(raw_file.as_str()) .with_context(|| error_messages::PARSE_CFG_FILE)?; - create_output_directory(&config, &abs_project_root)?; // NOTE: review if the must + create_output_directory(&config, &abs_project_root)?; // NOTE: review if we must // rebuilt the cache and model if the // output dir changes from // previous @@ -89,15 +90,6 @@ pub mod worker { program_data }; - let generate_commands_ts = Instant::now(); - generate_commands(&program_data, &mut cache) - .with_context(|| error_messages::FAILURE_GENERATING_COMMANDS)?; - - log::debug!( - "Zork++ took a total of {:?} ms on handling the generated commands", - generate_commands_ts.elapsed().as_millis() - ); - // Perform main work perform_main_work(cli_args, &program_data, &mut cache, cfg_path)?; // NOTE: study if we // must provide a flag to continue working with other cfgs (if present) if the current @@ -126,12 +118,22 @@ pub mod worker { Ok(false) } - fn perform_main_work( + fn perform_main_work<'a>( cli_args: &CliArgs, - program_data: &ZorkModel<'_>, - cache: &mut ZorkCache<'_>, + program_data: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, cfg_path: &Path, ) -> Result<()> { + let generate_commands_ts = Instant::now(); + + generate_commands_arguments(program_data, cache) + .with_context(|| error_messages::FAILURE_GENERATING_COMMANDS)?; + + log::debug!( + "Zork++ took a total of {:?} ms on handling the generated commands", + generate_commands_ts.elapsed().as_millis() + ); + let work_result = do_main_work_based_on_cli_input(cli_args, program_data, cache) .with_context(|| { format!( @@ -152,41 +154,28 @@ pub mod worker { program_data: &ZorkModel<'_>, cache: &mut ZorkCache<'_>, ) -> Result<()> { - let generated_commands = &mut cache.generated_commands; - - let general_args = generated_commands - .general_args - .as_mut() - .with_context(|| error_messages::GENERAL_ARGS_NOT_FOUND)? - .get_args(); - - let compiler_specific_shared_args = generated_commands - .compiler_common_args - .as_mut() - .with_context(|| error_messages::COMPILER_SPECIFIC_COMMON_ARGS_NOT_FOUND)? - .get_args(); - - let env_vars = match program_data.compiler.cpp_compiler { - CppCompiler::MSVC => &cache.compilers_metadata.msvc.env_vars, - CppCompiler::CLANG => &cache.compilers_metadata.clang.env_vars, - CppCompiler::GCC => &cache.compilers_metadata.gcc.env_vars, - }; + let compilers_metadata = &mut cache.compilers_metadata; + + let general_args = &mut cache.generated_commands.general_args; + let shared_args = &mut cache.generated_commands.compiler_common_args; + + let modules_generated_commands = &mut cache.generated_commands.modules; + let targets_generated_commands = &mut cache.generated_commands.targets; + + let flyweight_data = + FlyweightData::new(program_data, general_args, shared_args, compilers_metadata)?; executors::run_modules_generated_commands( program_data, - &general_args, - &compiler_specific_shared_args, - &mut generated_commands.modules, - env_vars, + &flyweight_data, + modules_generated_commands, )?; let target_executed_commands = executors::run_targets_generated_commands( program_data, - &general_args, - &compiler_specific_shared_args, - &mut generated_commands.targets, - &generated_commands.modules, - env_vars, + &flyweight_data, + targets_generated_commands, + modules_generated_commands, ); match cli_args.command { @@ -196,7 +185,7 @@ pub mod worker { // NOTE: study if it's worth to use the same loop for building and // autoexecuting, or otherwise, first build all, then autorun (actual // behaviour) - for (target_identifier, target_data) in generated_commands.targets.iter() { + for (target_identifier, target_data) in targets_generated_commands.iter() { if target_data.enabled_for_current_program_iteration { executors::autorun_generated_binary( &program_data.compiler.cpp_compiler, @@ -294,7 +283,6 @@ pub mod worker { ); cached_target.enabled_for_current_program_iteration = enabled; - // } } else { target_data.enabled_for_current_program_iteration = true; cached_target.enabled_for_current_program_iteration = true; From 8c04c9f5a5df4b54d6a344d2693bba80cdad7987 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 5 Aug 2024 19:16:02 +0200 Subject: [PATCH 68/73] chore: lint and fmt --- zork++/benches/benchmarks.rs | 4 ++-- zork++/src/lib/compiler/mod.rs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/zork++/benches/benchmarks.rs b/zork++/benches/benchmarks.rs index 5bb71815..5f86aa8c 100644 --- a/zork++/benches/benchmarks.rs +++ b/zork++/benches/benchmarks.rs @@ -4,7 +4,7 @@ use std::path::Path; use clap::Parser; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use zork::compiler::generate_commands; +use zork::compiler::generate_commands_arguments; use zork::{ cache::ZorkCache, cli::input::CliArgs, @@ -20,7 +20,7 @@ pub fn build_project_benchmark(c: &mut Criterion) { let mut cache = ZorkCache::default(); c.bench_function("Generate commands", |b| { - b.iter(|| generate_commands(black_box(&program_data), black_box(&mut cache))) + b.iter(|| generate_commands_arguments(black_box(&program_data), black_box(&mut cache))) }); /* c.bench_function("Cache loading time", |b| { diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 5d8b0e99..1b76ef06 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -32,7 +32,10 @@ pub mod data_factory; /// The core procedure. Generates the commands arguments that will be sent to the compiler /// for every translation unit declared by the user for its project -pub fn generate_commands_arguments<'a>(model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>) -> Result<()> { +pub fn generate_commands_arguments<'a>( + model: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, +) -> Result<()> { // Load the general args and the compiler specific ones if it's necessary load_flyweights_for_general_shared_data(model, cache); From f174ce27f4f8563107cc220c35016898f9397446 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 6 Aug 2024 18:08:49 +0200 Subject: [PATCH 69/73] feat: finished the work for mapping targets data from model to cache --- zork++/src/lib/domain/target.rs | 1 + zork++/src/lib/lib.rs | 245 ++++++++++++++++++++++++++------ 2 files changed, 205 insertions(+), 41 deletions(-) diff --git a/zork++/src/lib/domain/target.rs b/zork++/src/lib/domain/target.rs index ffb03bcf..113eb449 100644 --- a/zork++/src/lib/domain/target.rs +++ b/zork++/src/lib/domain/target.rs @@ -43,6 +43,7 @@ impl<'a> From<&'a str> for TargetIdentifier<'a> { } impl<'a> TargetIdentifier<'a> { + #[inline(always)] pub fn name(&'a self) -> &'a str { self.0.as_ref() } diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 047c7c3d..c8cff870 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -238,59 +238,30 @@ pub mod worker { } /// Helper to map the user declared targets on the [`ZorkModel`], previously mapped from the - /// [`ZorkConfigFile`] into the [`ZorkCache`], in order to avoid later calls with entry or insert - /// which will be hidden on the code an harder to read for newcomers or after time without - /// reading the codebase + /// [`ZorkConfigFile`] into the [`ZorkCache`] + /// Also, it takes care about enabling or disabling (based on their presence on the cfg during + /// the program iterations) and handles cache removes when they are deleted from the cfg fn map_model_targets_to_cache<'a>( program_data: &mut ZorkModel<'a>, cache: &mut ZorkCache<'a>, cli_args: &CliArgs, ) -> Result<()> { for (target_identifier, target_data) in program_data.targets.iter_mut() { - let target_name = target_identifier.name(); - // TODO: romper cada step en un único helper que puede ser unit tested - // 1st - Check if there's any new target to add to the tracked ones - if !cache - .generated_commands - .targets - .contains_key(target_identifier) - { - log::debug!("Adding a new target to the cache: {}", target_name); - cache.generated_commands.targets.insert( - target_identifier.clone(), - Target::new_default_for_kind(target_data.kind), - ); - } + helpers::add_new_target_to_cache(target_identifier, target_data, cache); // 2nd - Inspect the CliArgs to enable or disable targets for the current iteration - let cached_target = cache - .generated_commands - .targets - .get_mut(target_identifier) - .with_context(|| error_messages::TARGET_ENTRY_NOT_FOUND)?; - - if let Some(filtered_targets) = cli_args.targets.as_ref() { - // If there's Some(v), there's no need to check for emptyness on the underlying Vec - // (at least must be one) - let enabled = filtered_targets.iter().any(|t| t.eq(target_name)); - target_data.enabled_for_current_program_iteration = enabled; - // NOTE: we can perform the same check on the reader and rebuild the model if the - // cfg atrs changes via cli over iterations, avoiding having to mutate it here - log::info!( - "Target: {target_name} is {} from CLI for this iteration of Zork++", - if enabled { "enabled" } else { "disabled" } - ); - - cached_target.enabled_for_current_program_iteration = enabled; - } else { - target_data.enabled_for_current_program_iteration = true; - cached_target.enabled_for_current_program_iteration = true; - } + helpers::enable_and_disable_targets_based_on_cli_inputs( + target_identifier, + target_data, + cache, + cli_args, + )?; } - // TODO: 3rd - Remove from the cache the ones that the user removed from the cfg file (if they + // 3rd - Remove from the cache the ones that the user removed from the cfg file (if they // was tracked already) + helpers::delete_from_cache_removed_targets_from_cfg_file(program_data, cache); Ok(()) } @@ -362,16 +333,101 @@ pub mod worker { Ok(()) } + mod helpers { + use crate::domain::target::TargetIdentifier; + use project_model::target::TargetModel; + + use super::*; + + pub(crate) fn add_new_target_to_cache<'a>( + target_identifier: &TargetIdentifier<'a>, + target_data: &mut TargetModel<'a>, + cache: &mut ZorkCache<'a>, + ) { + if !cache + .generated_commands + .targets + .contains_key(target_identifier) + { + log::debug!( + "Adding a new target to the cache: {}", + target_identifier.name() + ); + cache.generated_commands.targets.insert( + target_identifier.clone(), + Target::new_default_for_kind(target_data.kind), + ); + } + } + + pub(crate) fn enable_and_disable_targets_based_on_cli_inputs<'a>( + target_identifier: &TargetIdentifier<'a>, + target_data: &mut TargetModel, + cache: &mut ZorkCache<'a>, + cli_args: &CliArgs, + ) -> Result<()> { + let target_name = target_identifier.name(); + + let cached_target = cache + .generated_commands + .targets + .get_mut(target_identifier) + .with_context(|| error_messages::TARGET_ENTRY_NOT_FOUND)?; + + if let Some(filtered_targets) = cli_args.targets.as_ref() { + // If there's Some(v), there's no need to check for emptyness on the underlying Vec + // (at least must be one) + let enabled = filtered_targets.iter().any(|t| t.eq(target_name)); + target_data.enabled_for_current_program_iteration = enabled; + // NOTE: we can perform the same check on the reader and rebuild the model if the + // cfg atrs changes via cli over iterations, avoiding having to mutate it here + + log::info!( + "Target: {target_name} is {} from CLI for this iteration of Zork++", + if enabled { "enabled" } else { "disabled" } + ); + + cached_target.enabled_for_current_program_iteration = enabled; + } else { + target_data.enabled_for_current_program_iteration = true; + cached_target.enabled_for_current_program_iteration = true; + }; + + Ok(()) + } + + pub(crate) fn delete_from_cache_removed_targets_from_cfg_file( + program_data: &ZorkModel, + cache: &mut ZorkCache, + ) { + let targets = &mut cache.generated_commands.targets; + targets.retain(|cached_target_identifier, _| { + program_data.targets.contains_key(cached_target_identifier) + }); + } + } + #[cfg(test)] mod tests { + use std::borrow::Cow; + use std::path::Path; + + use crate::cache::{self, ZorkCache}; + use crate::cli::input::CliArgs; + use crate::domain::target::TargetIdentifier; use crate::project_model::compiler::CppCompiler; + use crate::project_model::ZorkModel; + use crate::utils; use crate::utils::template::resources::CONFIG_FILE; + use clap::Parser; use color_eyre::Result; use tempfile::tempdir; use crate::config_file::{self, ZorkConfigFile}; use crate::utils::constants::{dir_names, ZORK}; + use super::{helpers, map_model_targets_to_cache}; + #[test] fn test_creation_directories() -> Result<()> { let temp = tempdir()?; @@ -410,5 +466,112 @@ pub mod worker { Ok(()) } + + #[test] + fn test_add_entry_to_cache() -> Result<()> { + let cli_args: CliArgs = CliArgs::parse_from(["", "build"]); + let zcf: ZorkConfigFile = config_file::zork_cfg_from_file(CONFIG_FILE)?; + let mut model: ZorkModel = utils::reader::build_model(zcf, &cli_args, Path::new("."))?; + let mut cache: ZorkCache = cache::ZorkCache::default(); + + for (target_identifier, target_data) in model.targets.iter_mut() { + helpers::add_new_target_to_cache(target_identifier, target_data, &mut cache); + assert!(cache + .generated_commands + .targets + .contains_key(target_identifier)); + assert!(!cache + .generated_commands + .targets + .contains_key(&TargetIdentifier(Cow::Borrowed("other")))); + } + + Ok(()) + } + + #[test] + fn test_enable_disable_targets_by_cli_input() -> Result<()> { + let cli_args: CliArgs = + CliArgs::parse_from(["", "--targets", "executable,tests", "build"]); + let zcf: ZorkConfigFile = config_file::zork_cfg_from_file(CONFIG_FILE)?; + let mut model: ZorkModel = utils::reader::build_model(zcf, &cli_args, Path::new("."))?; + let mut cache: ZorkCache = cache::ZorkCache::default(); + + // map_model_targets_to_cache(&mut model, &mut cache, &cli_args)?; + + for (target_identifier, target_data) in model.targets.iter_mut() { + helpers::add_new_target_to_cache(target_identifier, target_data, &mut cache); + helpers::enable_and_disable_targets_based_on_cli_inputs( + target_identifier, + target_data, + &mut cache, + &cli_args, + )?; + assert!(cache + .generated_commands + .targets + .contains_key(target_identifier)); + + let cached_target = cache + .generated_commands + .targets + .get(target_identifier) + .unwrap(); + assert!(cached_target.enabled_for_current_program_iteration); + } + + Ok(()) + } + + #[test] + fn test_clean_removed_targets_from_cfg() -> Result<()> { + let cli_args: CliArgs = CliArgs::parse_from(["", "--targets", "executable", "build"]); + let zcf: ZorkConfigFile = config_file::zork_cfg_from_file(CONFIG_FILE)?; + let mut model: ZorkModel = utils::reader::build_model(zcf, &cli_args, Path::new("."))?; + let mut cache: ZorkCache = cache::ZorkCache::default(); + + map_model_targets_to_cache(&mut model, &mut cache, &cli_args)?; + + let tests_ti = TargetIdentifier::from("tests"); + let tests = cache.generated_commands.targets.get(&tests_ti).unwrap(); + assert!(!tests.enabled_for_current_program_iteration); + + model.targets.retain(|k, _| k.ne(&tests_ti)); + map_model_targets_to_cache(&mut model, &mut cache, &cli_args)?; + + assert!(cache.generated_commands.targets.get(&tests_ti).is_none()); + + Ok(()) + } + + #[test] + fn test_map_model_targets_to_cache_and_enabled_status() -> Result<()> { + let cli_args: CliArgs = + CliArgs::parse_from(["", "--targets", "executable,tests", "build"]); + let zcf: ZorkConfigFile = config_file::zork_cfg_from_file(CONFIG_FILE)?; + let mut model: ZorkModel = utils::reader::build_model(zcf, &cli_args, Path::new("."))?; + let mut cache: ZorkCache = cache::ZorkCache::default(); + + map_model_targets_to_cache(&mut model, &mut cache, &cli_args)?; + + let cached_targets = cache.generated_commands.targets; + + for (target_identifier, _) in model.targets.iter_mut() { + let cached_target = cached_targets.get(target_identifier); + assert!(cached_target.is_some()); + } + + let executable = cached_targets + .get(&TargetIdentifier::from("executable")) + .unwrap(); + assert!(executable.enabled_for_current_program_iteration); + + let tests = cached_targets + .get(&TargetIdentifier::from("tests")) + .unwrap(); + assert!(tests.enabled_for_current_program_iteration); + + Ok(()) + } } } From 101c533a0a53cfdf933ccf7d421f2ebc14b838de Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 7 Aug 2024 10:06:19 +0200 Subject: [PATCH 70/73] feat: ensuring to remove from cache files deleted by the user --- zork++/src/lib/cache/mod.rs | 34 +++++++++++------------------ zork++/src/lib/lib.rs | 29 ++++++++++++++++++++++-- zork++/src/lib/project_model/mod.rs | 3 +-- zork++/src/lib/utils/constants.rs | 1 + 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index a42ff33f..33f24c92 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -94,8 +94,8 @@ pub struct ZorkCache<'a> { } impl<'a> ZorkCache<'a> { - pub fn save(&mut self, program_data: &ZorkModel<'_>, cli_args: &CliArgs) -> Result<()> { - self.run_final_tasks(program_data, cli_args)?; + pub fn save(&mut self, program_data: &ZorkModel<'_>) -> Result<()> { + self.run_final_tasks(program_data)?; self.metadata.last_program_execution = Utc::now(); utils::fs::save_file(&self.metadata.cache_file_path, self) @@ -212,21 +212,12 @@ impl<'a> ZorkCache<'a> { } /// Runs the tasks just before end the program and save the cache - fn run_final_tasks(&mut self, program_data: &ZorkModel<'_>, cli_args: &CliArgs) -> Result<()> { - let process_removals = Instant::now(); - let deletions_on_cfg = helpers::check_user_files_removals(self, program_data, cli_args); - log::debug!( - "Zork++ took a total of {:?} ms on checking and process removed items", - process_removals.elapsed().as_millis() - ); - + fn run_final_tasks(&mut self, program_data: &ZorkModel<'_>) -> Result<()> { if self.metadata.save_project_model { project_model::save(program_data, self)?; } - if program_data.project.compilation_db - && (self.metadata.generate_compilation_database || deletions_on_cfg) - { + if program_data.project.compilation_db && self.metadata.generate_compilation_database { let compile_commands_time = Instant::now(); compile_commands::map_generated_commands_to_compilation_db(program_data, self)?; log::debug!( @@ -456,7 +447,7 @@ mod msvc { } } -mod helpers { +pub(crate) mod helpers { use self::utils::constants::error_messages; use super::*; use crate::domain::translation_unit::TranslationUnitStatus; @@ -496,9 +487,8 @@ mod helpers { pub(crate) fn check_user_files_removals( cache: &mut ZorkCache, program_data: &ZorkModel<'_>, - _cli_args: &CliArgs, - ) -> bool { - remove_if_needed_from_cache_and_count_changes( + ) -> Result { + let deletions_on_cfg = remove_if_needed_from_cache_and_count_changes( &mut cache.generated_commands.modules.interfaces, &program_data.modules.interfaces, ) || remove_if_needed_from_cache_and_count_changes( @@ -516,14 +506,16 @@ mod helpers { .as_slice(), ); if changes { - return true; + return Ok(true); } } - return false; + return Ok(false); } || remove_if_needed_from_cache_and_count_changes( &mut cache.generated_commands.modules.system_modules, &program_data.modules.sys_modules, - ) + ); + + Ok(deletions_on_cfg) } fn remove_if_needed_from_cache_and_count_changes<'a, T: TranslationUnit<'a>>( @@ -538,6 +530,7 @@ mod helpers { if !r { log::debug!("Found translation_unit removed from cfg: {:?}", scl); + utils::fs:: } r } @@ -545,7 +538,6 @@ mod helpers { let total_cached_source_command_lines = cached_commands.len(); cached_commands.retain(removal_conditions); - // TODO: remove them also from the linker total_cached_source_command_lines > cached_commands.len() } diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index c8cff870..86e894ef 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -144,7 +144,7 @@ pub mod worker { }); // Save the cached data for this config file - cache.save(program_data, cli_args)?; + cache.save(program_data)?; work_result.with_context(|| format!("Failed to complete the job for: {:?}", cfg_path)) } @@ -230,13 +230,38 @@ pub mod worker { ) -> Result> { if meta_config_file.last_time_modified > cache.metadata.last_program_execution { cache.metadata.save_project_model = true; - utils::reader::build_model(zork_config_file, cli_args, abs_project_root) + let project_model = + utils::reader::build_model(zork_config_file, cli_args, abs_project_root)?; + + // Check for the changes made by the user on the cfg + check_for_changes_in_cfg(&project_model, cache) + .with_context(|| error_messages::CHECK_FOR_DELETIONS)?; + + Ok(project_model) } else { log::debug!("Loading the ZorkModel from the cache"); project_model::load(cache) } } + fn check_for_changes_in_cfg( + project_model: &ZorkModel, + cache: &mut ZorkCache<'_>, + ) -> Result<()> { + let process_removals = Instant::now(); + let deletions_on_cfg = cache::helpers::check_user_files_removals(cache, project_model); + log::debug!( + "Zork++ took a total of {:?} ms on checking and process removed items", + process_removals.elapsed().as_millis() + ); + + if deletions_on_cfg? { + cache.metadata.generate_compilation_database = true; + } + + Ok(()) + } + /// Helper to map the user declared targets on the [`ZorkModel`], previously mapped from the /// [`ZorkConfigFile`] into the [`ZorkCache`] /// Also, it takes care about enabling or disabling (based on their presence on the cfg during diff --git a/zork++/src/lib/project_model/mod.rs b/zork++/src/lib/project_model/mod.rs index e6ba242a..6b675e65 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -5,12 +5,11 @@ pub mod project; pub mod sourceset; pub mod target; -use std::fmt::Debug; - use color_eyre::eyre::Context; use color_eyre::Result; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; +use std::fmt::Debug; use crate::utils::constants::error_messages; use crate::{cache::ZorkCache, domain::target::TargetIdentifier}; diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index 9fc42142..0e94634d 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -38,6 +38,7 @@ pub mod error_messages { pub const FAILURE_LOADING_INITIAL_CACHE_DATA: &str = "Failed to load the cache initial data"; pub const FAILURE_CLEANING_CACHE: &str = "Error cleaning the Zork++ cache"; pub const FAILURE_SAVING_CACHE: &str = "Error saving data to the Zork++ cache"; + pub const CHECK_FOR_DELETIONS: &str = "Error while checking the user files deletions on cfg"; pub const GENERAL_ARGS_NOT_FOUND: &str = "Something went wrong loading the general arguments"; pub const PROJECT_MODEL_MAPPING: &str = "Error building the project model"; pub const PROJECT_MODEL_LOAD: &str = "Error loading from the fs the project model"; From 3ac924d03669d86836e37bcf5bd459758841f36f Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 7 Aug 2024 12:02:04 +0200 Subject: [PATCH 71/73] chore: code cleanup, ultimate perf upgrades before the release --- zork++/src/lib/cache/mod.rs | 7 +++--- zork++/src/lib/cli/output/executors.rs | 35 +++++++++----------------- zork++/src/lib/compiler/mod.rs | 13 +++++----- zork++/src/lib/lib.rs | 16 +++--------- zork++/src/lib/utils/constants.rs | 1 + zork++/src/lib/utils/fs.rs | 9 +++++++ 6 files changed, 37 insertions(+), 44 deletions(-) diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 33f24c92..0174b2e6 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -448,6 +448,8 @@ mod msvc { } pub(crate) mod helpers { + use color_eyre::eyre::ContextCompat; + use self::utils::constants::error_messages; use super::*; use crate::domain::translation_unit::TranslationUnitStatus; @@ -501,7 +503,7 @@ pub(crate) mod helpers { program_data .targets .get(target_name) - .unwrap() + .with_context(|| error_messages::TARGET_ENTRY_NOT_FOUND)? .sources .as_slice(), ); @@ -522,7 +524,7 @@ pub(crate) mod helpers { cached_commands: &mut Vec, user_declared_translation_units: &[T], ) -> bool { - let removal_conditions = |scl: &SourceCommandLine| { + let removal_conditions = |scl: &SourceCommandLine| -> bool { scl.status.eq(&TranslationUnitStatus::ToDelete) || { let r = user_declared_translation_units .iter() @@ -530,7 +532,6 @@ pub(crate) mod helpers { if !r { log::debug!("Found translation_unit removed from cfg: {:?}", scl); - utils::fs:: } r } diff --git a/zork++/src/lib/cli/output/executors.rs b/zork++/src/lib/cli/output/executors.rs index 95391dbd..812d7db1 100644 --- a/zork++/src/lib/cli/output/executors.rs +++ b/zork++/src/lib/cli/output/executors.rs @@ -222,9 +222,6 @@ mod helpers { } else { [].iter() }) - // NOTE: The embedeed if above allows us to avoid to clone iterators by reasining data - // if the compiler is GCC, where we don't want to chain the system modules since GCC - // already handles their compilation products itself (gcm.cache) .map(|scl| &scl.byproduct); let args = shared_args @@ -254,14 +251,7 @@ mod helpers { generated_commands: &mut ModulesCommands<'_>, ) -> Result<()> { let translation_units_commands: Vec<&mut SourceCommandLine> = - get_modules_translation_units_commands( - // Independent borrows to avoid have borrow checker yielding at me - &mut generated_commands.cpp_stdlib, - &mut generated_commands.c_compat_stdlib, - &mut generated_commands.system_modules, - &mut generated_commands.interfaces, - &mut generated_commands.implementations, - ); + get_modules_translation_units_commands(generated_commands); if translation_units_commands.is_empty() { log::debug!("No modules to process, build or rebuild in this iteration."); @@ -305,21 +295,20 @@ mod helpers { Ok(()) } - /// TODO: create strong types or aliases at least pub(crate) fn get_modules_translation_units_commands<'a, 'b>( - cpp_stdlib: &'b mut Option>, - c_compat_stdlib: &'b mut Option>, - system_modules: &'b mut Vec>, - interfaces: &'b mut Vec>, - implementations: &'b mut Vec>, + generated_commands: &'b mut ModulesCommands<'a>, ) -> Vec<&'b mut SourceCommandLine<'a>> { + let cpp_stdlib = generated_commands.cpp_stdlib.as_mut_slice().iter_mut(); + let c_compat_stdlib = generated_commands.c_compat_stdlib.as_mut_slice().iter_mut(); + let system_modules = generated_commands.system_modules.as_mut_slice().iter_mut(); + let interfaces = generated_commands.interfaces.as_mut_slice().iter_mut(); + let implementations = generated_commands.implementations.as_mut_slice().iter_mut(); + cpp_stdlib - .as_mut_slice() - .iter_mut() - .chain(c_compat_stdlib.as_mut_slice().iter_mut()) - .chain(system_modules.as_mut_slice().iter_mut()) - .chain(interfaces.as_mut_slice().iter_mut()) - .chain(implementations.as_mut_slice().iter_mut()) + .chain(c_compat_stdlib) + .chain(system_modules) + .chain(interfaces) + .chain(implementations) .filter(|scl| scl.status.eq(&TranslationUnitStatus::PendingToBuild)) .collect::>() } diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 1b76ef06..ea8d5767 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -178,7 +178,7 @@ fn generate_linkage_targets_commands<'a>( ) -> Result<()> { log::info!( "Generating the linker command line for target: {:?}", - &target.0 + &target.0.name() ); let target_identifier = target.0; @@ -524,9 +524,6 @@ mod sources { arguments.push(clang_args::add_prebuilt_module_path(compiler, out_dir)); } - // arguments.extend_from_to_argument_slice(&target.extra_args); // TODO: add them as flyweight - // data on the executors - let obj_file = helpers::generate_obj_file(compiler, out_dir, source); let fo = if compiler.eq(&CppCompiler::MSVC) { "/Fo" @@ -555,8 +552,12 @@ mod sources { } } -/// Helpers for reduce the cyclomatic complexity -mod helpers { +/// Helpers for reduce the cyclomatic complexity of generating command lines, arguments +/// and in other cases, paths depending on what kind of [`TranslationUnitKind`] we are +/// processing +/// +/// This module is actually public(crate) reexported since we need to +pub(crate) mod helpers { use super::*; use crate::domain::commands::command_lines::SourceCommandLine; use crate::domain::translation_unit::TranslationUnitStatus; diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 86e894ef..8721562d 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -234,7 +234,7 @@ pub mod worker { utils::reader::build_model(zork_config_file, cli_args, abs_project_root)?; // Check for the changes made by the user on the cfg - check_for_changes_in_cfg(&project_model, cache) + check_for_deletions_in_cfg(&project_model, cache) .with_context(|| error_messages::CHECK_FOR_DELETIONS)?; Ok(project_model) @@ -244,7 +244,9 @@ pub mod worker { } } - fn check_for_changes_in_cfg( + /// Little helper to check if the user remove files from the [`ZorkConfigFile`] and therefore, + /// they should be removed from the cache + fn check_for_deletions_in_cfg( project_model: &ZorkModel, cache: &mut ZorkCache<'_>, ) -> Result<()> { @@ -317,13 +319,6 @@ pub mod worker { .unwrap_or("out"); let out_dir = Path::new(project_root).join(binding); - if out_dir.exists() { - return Ok(()); // TODO: remeber that this causes a bug - } // early guard. If the out_dir already exists, all - // the sub-structure must exists and be correct. - // Otherwise, a full out dir wipe will be preferable - // that checking if they all exists on every run - // Recursively create the directories below and all of its parent components if they are missing let modules_path = out_dir.join(compiler_name).join(dir_names::MODULES); @@ -404,9 +399,6 @@ pub mod worker { // (at least must be one) let enabled = filtered_targets.iter().any(|t| t.eq(target_name)); target_data.enabled_for_current_program_iteration = enabled; - // NOTE: we can perform the same check on the reader and rebuild the model if the - // cfg atrs changes via cli over iterations, avoiding having to mutate it here - log::info!( "Target: {target_name} is {} from CLI for this iteration of Zork++", if enabled { "enabled" } else { "disabled" } diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index 0e94634d..43d88367 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -26,6 +26,7 @@ pub mod debug_messages { pub mod error_messages { pub const READ_CFG_FILE: &str = "Could not read the configuration file"; pub const PARSE_CFG_FILE: &str = "Could not parse the configuration file"; + pub const REMOVE_FILE: &str = "Unable to remove file from fs"; pub const FAILURE_GENERATING_COMMANDS: &str = "Failed to generated the commands for the project"; pub const FAILED_BUILD_FOR_CFG_FILE: &str = "Failed to build the project for the config file"; diff --git a/zork++/src/lib/utils/fs.rs b/zork++/src/lib/utils/fs.rs index 9b792bbe..f2134042 100644 --- a/zork++/src/lib/utils/fs.rs +++ b/zork++/src/lib/utils/fs.rs @@ -8,6 +8,8 @@ use std::{ }; use walkdir::WalkDir; +use super::constants::error_messages; + /// Creates a new file in the filesystem if the given does not exists yet at the specified location pub fn create_file<'a>(path: &Path, filename: &'a str, buff_write: &'a [u8]) -> Result<()> { let file_path = path.join(filename); @@ -35,6 +37,13 @@ pub fn find_file(search_root: &Path, target_filename: &str) -> Option Result<()> { + if path.exists() { + return std::fs::remove_file(path).with_context(|| error_messages::REMOVE_FILE); + } + Ok(()) +} + /// Recursively creates a new directory pointed at the value of target if not exists yet pub fn create_directory(target: &Path) -> Result<()> { if !target.exists() { From f5900260bd88bda4921f8f95585282f2ff14a3aa Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 7 Aug 2024 13:01:33 +0200 Subject: [PATCH 72/73] docs: Targets documentation --- README.md | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 2fe63e3f..7ef9c1db 100644 --- a/README.md +++ b/README.md @@ -271,23 +271,31 @@ It is used to specify whether to use the `libc++` or `libstdc++` Standard librar - `[modules]` ⇒ The core section to instruct the compiler to work with `C++20 modules`. The most important attributes are the base path to the *interfaces* and *implementation* files (usually you don't want to spread your files elsewhere). `Zork++` only looks for your `interfaces` and `implementation` files in the respective directories. -- `[executable]` ⇒ Whenever `Zork++` sees this attribute, it knows that he must produce an executable. You must specify the name of the generated binary and the sources that will be taken into consideration to produce that executable. - -- `[tests]` ⇒ The `tests` section allows you to run the tests written for your application in a convenient way. -You only have to provide the sources, and you are ready to go! -`Zork++` will take care of the rest. - -For now, tests run independent of the executables or library generations. -So if you want to check the health of your applications and run your tests, just invoke the `test` command. - -`$ zork++ -v test` - -You must manually provide a test framework for now. -There are plans to include support for one or more of the major ones with `Zork++` directly, like `boost::ut` or `Catch2`. -But for now, you must manually download the source files and pass them (if applies) to `Zork++`. +- `[targets.]` ⇒ Targets are named entries that are meant to produce a final product. Currently, +`Zork++` can produce *binary* products, *static libraries* and/or *dynamic libraries*. + - You can have as many *targets* you want and or you need. Or the reference section of this doc, you'll find all the +different options that you can use to configure every target. + - A very important note is that targets are processed in the declaration order of the configuration file, so be aware +of this fact when you do design your product lifecycle. + - Also, + +Tests run independent of the executables or library generations. They are a shortcut alias to --targets +but with the particular behaviour that it will filter all the targets that include after the `.` in its +identifier the *word* `test` (any position, clasical 'contains'). Of course, you can mix them with the `--targets` +flag, so only *test* targets (with the *test* in its identifier) and the ones provided in the *CLI* argument +will be taking in consideration. + +For example. Suppose that you have three different targets to test three different areas of your project. +Let's name them `test1`, `test2` and `test3`. + +```bash +$ zork++ -v --targets test2,test3 test +``` -The optional `base_path` property allows you to specify a path where `Zork++` looks for your source files, so you don't have to specify the full path for every source. +For this `Zork++` invokation, it will only be processed and executed `test2` and `test3`. +To summarize: the `test` argument implies `build` + `run` but only for targets that contains a `test` substring +within its target identifier (which is the string after the `.` on the `[targets.]`) entry. ## :bulb: Additional notes on the `[modules]` attribute @@ -374,7 +382,7 @@ ProjectAttribute { name: str authors: Option>, compilation_db : bool - code_root: str + code_root: str // A joinable path after the project root to add to every translation unit } /// The [compiler] key @@ -514,7 +522,7 @@ To summarize, we are offering the following commands and arguments: - `build` ⇒ just compiles the project for every target declared (unless filtered by cli args) - `run` ⇒ compiles the project and then runs the generated binary for every target declared (unless filtered by cli args) - `test` ⇒ compiles the project and then runs the binary generated for any [`targets.`] (unless filtered by cli args) -- `new` ⇒ generates a new `C++20` onwards template project with a minimal configuration and +- `new` ⇒ generates a new `C++2X` template project with a minimal configuration and a minimal setup. This command includes some arguments to make it more flexible, like: - `--name ` ⇒ the name of the autogenerated project - `--git` ⇒ initializes a new git empty repository From 71a1c8a74e6e0043eeff584542a88cb2d6aa908f Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 7 Aug 2024 16:27:18 +0200 Subject: [PATCH 73/73] feat: test targets are manually picked by name when they contains test on their identifier --- zork++/src/lib/compiler/mod.rs | 2 +- zork++/src/lib/lib.rs | 46 +++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index ea8d5767..aad757c7 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -157,7 +157,7 @@ fn generate_sources_cmds_args<'a>( log::info!( "Generating the commands for the source files of target: {:?}", - target_identifier + target_identifier.name() ); process_kind_translation_units( diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index 8721562d..ea9d5be0 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -182,9 +182,6 @@ pub mod worker { Command::Build => target_executed_commands, Command::Run | Command::Test => match target_executed_commands { Ok(_) => { - // NOTE: study if it's worth to use the same loop for building and - // autoexecuting, or otherwise, first build all, then autorun (actual - // behaviour) for (target_identifier, target_data) in targets_generated_commands.iter() { if target_data.enabled_for_current_program_iteration { executors::autorun_generated_binary( @@ -266,7 +263,7 @@ pub mod worker { /// Helper to map the user declared targets on the [`ZorkModel`], previously mapped from the /// [`ZorkConfigFile`] into the [`ZorkCache`] - /// Also, it takes care about enabling or disabling (based on their presence on the cfg during + /// Also, it takes care about enabling or disabling targets (based on their presence on the cfg during /// the program iterations) and handles cache removes when they are deleted from the cfg fn map_model_targets_to_cache<'a>( program_data: &mut ZorkModel<'a>, @@ -278,7 +275,7 @@ pub mod worker { helpers::add_new_target_to_cache(target_identifier, target_data, cache); // 2nd - Inspect the CliArgs to enable or disable targets for the current iteration - helpers::enable_and_disable_targets_based_on_cli_inputs( + helpers::enable_or_disable_target_based_on_cli_inputs( target_identifier, target_data, cache, @@ -286,6 +283,16 @@ pub mod worker { )?; } + log::info!( + "Target enabled for this iteration of Zork++: {:?}", + program_data + .targets + .iter() + .filter(|(_, data)| data.enabled_for_current_program_iteration) + .map(|(id, _)| id.name()) + .collect::>() + ); + // 3rd - Remove from the cache the ones that the user removed from the cfg file (if they // was tracked already) helpers::delete_from_cache_removed_targets_from_cfg_file(program_data, cache); @@ -380,7 +387,7 @@ pub mod worker { } } - pub(crate) fn enable_and_disable_targets_based_on_cli_inputs<'a>( + pub(crate) fn enable_or_disable_target_based_on_cli_inputs<'a>( target_identifier: &TargetIdentifier<'a>, target_data: &mut TargetModel, cache: &mut ZorkCache<'a>, @@ -394,22 +401,25 @@ pub mod worker { .get_mut(target_identifier) .with_context(|| error_messages::TARGET_ENTRY_NOT_FOUND)?; - if let Some(filtered_targets) = cli_args.targets.as_ref() { - // If there's Some(v), there's no need to check for emptyness on the underlying Vec - // (at least must be one) + let enabled = if let Some(filtered_targets) = cli_args.targets.as_ref() { + // If there's Some(v), there's no need to check for emptyness on the underlying Vec (at least must be one) let enabled = filtered_targets.iter().any(|t| t.eq(target_name)); - target_data.enabled_for_current_program_iteration = enabled; - log::info!( - "Target: {target_name} is {} from CLI for this iteration of Zork++", - if enabled { "enabled" } else { "disabled" } - ); + enabled + } else { + true + }; - cached_target.enabled_for_current_program_iteration = enabled; + // If it's a [CliArgs::Test] command invokation, enable only the ones that contains + // a <*test*> string in its identifier + let enabled = if cli_args.command.eq(&Command::Test) { + target_name.contains("test") } else { - target_data.enabled_for_current_program_iteration = true; - cached_target.enabled_for_current_program_iteration = true; + enabled }; + target_data.enabled_for_current_program_iteration = enabled; + cached_target.enabled_for_current_program_iteration = enabled; + Ok(()) } @@ -518,7 +528,7 @@ pub mod worker { for (target_identifier, target_data) in model.targets.iter_mut() { helpers::add_new_target_to_cache(target_identifier, target_data, &mut cache); - helpers::enable_and_disable_targets_based_on_cli_inputs( + helpers::enable_or_disable_target_based_on_cli_inputs( target_identifier, target_data, &mut cache,