From 2800fe7c0bf9daf43e03cbd95d8a7ea31683a681 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 14 Jul 2024 12:02:56 +0200 Subject: [PATCH] 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(),