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/.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/README.md b/README.md index 2b752878..7ef9c1db 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 @@ -228,22 +228,10 @@ 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 = [ - { file = 'math.cppm' } +interfaces = [ + { file = 'math.cppm' } ] base_impls_dir = "./github-example/src" implementations = [ @@ -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,38 +268,46 @@ 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. -- `[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 > 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 +330,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 +350,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 +377,15 @@ ZorkConfigFile { tests: Option, } -/// The [project] key +/// The [project] key ProjectAttribute { - name: &'a str + name: str authors: Option>, compilation_db : bool + code_root: str // A joinable path after the project root to add to every translation unit } -/// 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,17 +394,29 @@ CompilerAttribute { extra_args: Option> } -/// The [build] key +/// The [build] key 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 @@ -404,44 +425,33 @@ 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 -TestsAttribute { - test_executable_name: Option, - sources_base_path: Option, - sources: Option>, - extra_args: Option>, -} -``` - ## A closer look on the `ModulesAttribute` key ```Rust /// [`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 +470,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 @@ -497,7 +507,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 @@ -506,10 +519,10 @@ 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 -- `new` ⇒ generates a new `C++20` onwards template project with a minimal configuration and +- `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++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 @@ -518,6 +531,11 @@ 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. +- `--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. @@ -529,9 +547,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 @@ -557,19 +573,18 @@ 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. - 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. + - 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). +- `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++/.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++/Cargo.lock b/zork++/Cargo.lock index ce34500a..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.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +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]] @@ -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.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" [[package]] name = "cfg-if" @@ -214,15 +214,15 @@ checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "bitflags 1.3.2", "clap_lex 0.2.4", - "indexmap", + "indexmap 1.9.3", "textwrap", ] [[package]] name = "clap" -version = "4.5.4" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", @@ -230,21 +230,21 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.0", + "clap_lex 0.7.2", "strsim", ] [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", @@ -263,9 +263,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +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" @@ -361,15 +361,15 @@ 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" -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", @@ -388,6 +388,22 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "errno" version = "0.3.9" @@ -395,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]] @@ -442,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" @@ -499,14 +521,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] +[[package]] +name = "indexmap" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", + "serde", +] + +[[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" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -534,9 +573,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" @@ -552,21 +591,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", ] @@ -597,9 +636,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" @@ -643,9 +682,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", ] @@ -681,9 +720,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -693,9 +732,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", @@ -704,9 +743,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" @@ -720,11 +759,11 @@ 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", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -744,18 +783,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", @@ -764,11 +803,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -781,9 +821,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.66" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -792,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]] @@ -808,6 +849,26 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -827,6 +888,57 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf" + +[[package]] +name = "typetag" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7ec175048b96728c30152928c52161bfcc8ea2bd3fb7ed4ccb7dec060b2834" +dependencies = [ + "erased-serde", + "inventory", + "once_cell", + "serde", + "typetag-impl", +] + +[[package]] +name = "typetag-impl" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b5474fd169a5b02b6782b56bbbbff27e85947d4488e5501123687db3148647" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -835,9 +947,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" @@ -931,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]] @@ -962,11 +1074,20 @@ 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.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", @@ -980,67 +1101,70 @@ 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.13", "color-eyre", "criterion", "env_logger", "glob", + "indexmap 2.3.0", "log", "regex", "serde", "serde_json", "tempfile", "toml", + "transient", + "typetag", "walkdir", ] diff --git a/zork++/Cargo.toml b/zork++/Cargo.toml index 2b599124..b1c6bd95 100644 --- a/zork++/Cargo.toml +++ b/zork++/Cargo.toml @@ -18,6 +18,9 @@ path = "src/bin/main.rs" toml = "0.5.11" glob = "0.3.1" serde = { version = "1.0.202", features = ["derive"] } +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" env_logger = "0.11.3" diff --git a/zork++/benches/benchmarks.rs b/zork++/benches/benchmarks.rs index 54305ec5..5f86aa8c 100644 --- a/zork++/benches/benchmarks.rs +++ b/zork++/benches/benchmarks.rs @@ -4,10 +4,10 @@ use std::path::Path; use clap::Parser; use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use zork::compiler::generate_commands_arguments; use zork::{ - cache::{self, ZorkCache}, + cache::ZorkCache, cli::input::CliArgs, - compiler::build_project, config_file::{self, ZorkConfigFile}, utils::{self, reader::build_model}, }; @@ -16,16 +16,16 @@ 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| { - b.iter(|| build_project(black_box(&program_data), black_box(&mut cache), false)) + c.bench_function("Generate commands", |b| { + b.iter(|| generate_commands_arguments(black_box(&program_data), black_box(&mut cache))) }); - 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/bounds/mod.rs b/zork++/src/lib/bounds/mod.rs deleted file mode 100644 index 832253d5..00000000 --- a/zork++/src/lib/bounds/mod.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! The higher abstractions of the program - -use core::fmt::Debug; -use std::fmt::Display; -use std::path::PathBuf; - -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<'a>]; -} - -/// Contracts for the executable operations -pub trait ExecutableTarget<'a>: ExtraArgs<'a> { - fn name(&'a self) -> &'a str; - fn sourceset(&'a self) -> &'a SourceSet; -} - -/// Represents any kind of translation unit and the generic operations -/// applicable to all the implementors -pub trait TranslationUnit: Display + Debug { - /// Returns the file, being the addition of the path property plus the file stem plus - /// the extension property - 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) -> String; - - /// Outputs the declared extension for `self` - fn extension(&self) -> String; - - /// 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 59fe83fc..b1a68494 100644 --- a/zork++/src/lib/cache/compile_commands.rs +++ b/zork++/src/lib/cache/compile_commands.rs @@ -1,20 +1,64 @@ use crate::cache::ZorkCache; + +use crate::domain::commands::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}; +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<()> { - log::trace!("Generating the compilation database..."); - let mut compilation_db_entries = Vec::with_capacity(cache.last_generated_commands.len()); +pub(crate) fn map_generated_commands_to_compilation_db( + program_data: &ZorkModel, + cache: &mut ZorkCache, +) -> 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()); + + 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 command in cache.last_generated_commands.iter() { - compilation_db_entries.push(CompileCommands::from(command)); + 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::>(); + + 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); @@ -22,30 +66,16 @@ 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) + + 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)] -pub struct CompileCommands { - pub directory: String, - pub file: String, - pub arguments: Vec, -} - -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()); - - Self { - directory: dir.to_str().unwrap_or_default().to_string(), - file: file.to_str().unwrap_or_default().to_string(), - arguments: value.1.clone(), - } - } +#[derive(Serialize, Debug)] +pub struct CompileCommand<'a> { + 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 03f42cbe..0174b2e6 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -3,247 +3,356 @@ 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, path::{Path, PathBuf}, + time::Instant, }; +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}; use crate::{ - cli::{ - input::CliArgs, - output::commands::{CommandExecutionResult, Commands, SourceCommandLine}, - }, + cli::input::CliArgs, + project_model, project_model::{compiler::CppCompiler, ZorkModel}, - utils::{ - self, - constants::{self, GCC_CACHE_DIR}, - }, + utils::{self}, }; use serde::{Deserialize, Serialize}; -use walkdir::WalkDir; - -/// Standalone utility for retrieve the Zork++ cache file -pub fn load(program_data: &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")?; - } 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 subdir for {compiler}")?; - 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; +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`] +pub fn load<'a>( + config: &ZorkConfigFile<'a>, + cli_args: &CliArgs, + project_root: &Path, +) -> Result> { + let compiler: CppCompiler = config.compiler.cpp_compiler.into(); + let output_dir = Path::new(project_root).join( + config + .build + .as_ref() + .and_then(|build_attr| build_attr.output_dir) + .unwrap_or(dir_names::DEFAULT_OUTPUT_DIR), + ); + let cache_path = output_dir.join(constants::ZORK).join(dir_names::CACHE); + + let cache_file_path = cache_path + .join(compiler.as_ref()) + .with_extension(constants::CACHE_FILE_EXT); + + 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 { + 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: {:?}", + cache_file_path + ); + utils::fs::load_and_deserialize(&cache_file_path) + .with_context(|| "Error loading the Zork++ cache")? + }; - cache - .run_tasks(program_data) - .with_context(|| "Error running the cache tasks")?; + cache.metadata.process_no += 1; Ok(cache) } -/// Standalone utility for persist the cache to the file system -pub fn save( - program_data: &ZorkModel<'_>, - cache: &mut 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); - - cache.run_final_tasks(program_data, commands, 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 compilers_metadata: CompilersMetadata<'a>, + pub generated_commands: Commands<'a>, + pub metadata: CacheMetadata, } -#[derive(Deserialize, Serialize, Debug, Default, Clone)] -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, -} +impl<'a> ZorkCache<'a> { + 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) + .with_context(|| error_messages::FAILURE_SAVING_CACHE) + } -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 get_cmd_for_translation_unit_kind>( + &mut self, + translation_unit: &T, + 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(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(), + StdLibMode::CCompat => self.get_ccompat_stdlib_cmd(), + }, } - None } - /// The tasks associated with the cache after load it from the file system - 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)? + fn get_module_ifc_cmd>( + &mut self, + module_interface: &T, + ) -> Option<&mut SourceCommandLine<'a>> { + self.generated_commands + .modules + .interfaces + .iter_mut() + .find(|cached_tu| module_interface.path().eq(&cached_tu.path())) + } + + fn get_module_impl_cmd>( + &mut self, + module_impl: &T, + ) -> Option<&mut SourceCommandLine<'a>> { + self.generated_commands + .modules + .implementations + .iter_mut() + .find(|cached_tu| module_impl.path().eq(&cached_tu.path())) + } + + fn get_source_cmd>( + &mut self, + source: &T, + for_target: &TargetIdentifier<'a>, + ) -> Option<&mut SourceCommandLine<'a>> { + self.generated_commands + .targets + .get_mut(for_target) + .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 + /// + /// 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, + 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())) + } + + pub fn get_cpp_stdlib_cmd_by_kind( + &mut self, + stdlib_mode: StdLibMode, + ) -> Option<&mut SourceCommandLine<'a>> { + match stdlib_mode { + StdLibMode::Cpp => self.generated_commands.modules.cpp_stdlib.as_mut(), + StdLibMode::CCompat => self.generated_commands.modules.c_compat_stdlib.as_mut(), } + } - if compiler != CppCompiler::MSVC { - let i = Self::track_system_modules(program_data); - self.compilers_metadata.system_modules.clear(); - self.compilers_metadata.system_modules.extend(i); + pub fn set_cpp_stdlib_cmd_by_kind( + &mut self, + stdlib_mode: StdLibMode, + cmd_line: Option>, + ) { + match stdlib_mode { + 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.modules.cpp_stdlib.as_mut() + } + + fn get_ccompat_stdlib_cmd(&mut self) -> Option<&mut SourceCommandLine<'a>> { + self.generated_commands.modules.c_compat_stdlib.as_mut() + } + + /// The tasks associated with the cache after load it from the file system + 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(()) } /// Runs the tasks just before end the program and save the cache - pub fn run_final_tasks( - &mut self, - program_data: &ZorkModel<'_>, - mut commands: Commands<'_>, - test_mode: bool, - ) -> Result<()> { - if self.save_generated_commands(&mut commands, program_data, test_mode) - && program_data.project.compilation_db - { - compile_commands::map_generated_commands_to_compilation_db(self)?; + 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.compiler.cpp_compiler == CppCompiler::MSVC) { - self.compilers_metadata.system_modules = program_data - .modules - .sys_modules - .iter() - .map(|e| e.to_string()) - .collect::>(); + 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!( + "Zork++ took a total of {:?} ms on generate the compilation database", + compile_commands_time.elapsed().as_millis() + ); } Ok(()) } - fn save_generated_commands( - &mut self, - commands: &mut Commands<'_>, - model: &ZorkModel, - test_mode: bool, - ) -> 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 - } else { - 1 - }; + /// 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, + CppCompiler::CLANG => &self.compilers_metadata.clang.env_vars, + CppCompiler::GCC => &self.compilers_metadata.gcc.env_vars, + } + } - 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(), - }; + /// 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; + + generated_commands + .modules + .cpp_stdlib + .as_slice() + .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()), + ) + } - 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(" "), - }; + /// 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.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() + } +} - 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()); +/// 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, + 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 save_project_model: bool, +} - self.generated_commands.push(commands_details); +/// Type alias for the underlying key-value based collection of environmental variables +pub type EnvVars = HashMap; - are_new_commands.iter().any(|b| *b) - } +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +pub struct CompilersMetadata<'a> { + pub msvc: MsvcMetadata<'a>, + pub clang: ClangMetadata, + pub gcc: GccMetadata, +} + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +pub struct MsvcMetadata<'a> { + pub compiler_version: Option, + pub dev_commands_prompt: Option, + 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 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, +} + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +pub struct ClangMetadata { + pub env_vars: EnvVars, +} - /// If Windows is the current OS, and the compiler is MSVC, then we will try +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +pub struct GccMetadata { + pub env_vars: EnvVars, +} + +/// 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::utils; + 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; + 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 /// 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; + pub(crate) fn load_metadata( + cache: &mut ZorkCache, + 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, @@ -256,32 +365,43 @@ impl ZorkCache { }); 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 = Self::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(); + 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(env_vars::VC_TOOLS_INSTALL_DIR) + .with_context(|| error_messages::msvc::MISSING_VCTOOLS_DIR)?; - let vs_stdlib_path = - Path::new(msvc.env_vars.get("VCToolsInstallDir").unwrap()).join("modules"); - msvc.vs_stdlib_path = Some(SourceFile { + 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)); + } + + msvc.vs_stdlib_path = 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 { + file_stem: Cow::Borrowed("std"), + extension: compiler.default_module_extension(), + }; + msvc.vs_ccompat_stdlib_path = 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) + file_stem: Cow::Borrowed("std.compat"), + extension: compiler.default_module_extension(), + }; + 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 @@ -292,10 +412,10 @@ impl ZorkCache { 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 + 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.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()); } @@ -304,7 +424,7 @@ impl ZorkCache { /// 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> { + 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(); @@ -312,7 +432,10 @@ impl ZorkCache { 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(); @@ -322,189 +445,101 @@ impl ZorkCache { 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() - }) - } +pub(crate) mod helpers { + use color_eyre::eyre::ContextCompat; - fn normalize_execution_result_status( - &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 - } - } + use self::utils::constants::error_messages; + use super::*; + use crate::domain::translation_unit::TranslationUnitStatus; + use std::path::PathBuf; - fn extend_collection_of_source_file_details( - &mut self, - model: &ZorkModel, - collection: &mut Vec, - target: &mut [SourceCommandLine], + pub(crate) fn initialize_cache<'a>( + cache_path: PathBuf, + cache_file_path: PathBuf, 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), - } - })); + output_dir: &Path, + ) -> Result> { + 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, + project_model_file_path, + ..Default::default() + }, + ..Default::default() + }; - new_commands - } + cache + .run_tasks(compiler, output_dir) + .with_context(|| error_messages::FAILURE_LOADING_INITIAL_CACHE_DATA)?; - /// Method that returns the HashMap that holds the enviromental variables that must be passed - /// to the underlying shell - pub fn get_process_env_args(&self) -> &EnvVars { - match self.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, - } + Ok(cache) } -} -#[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, - pub execution_result: CommandExecutionResult, -} + /// 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<'_>, + ) -> 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( + &mut cache.generated_commands.modules.implementations, + &program_data.modules.implementations, + ) || { + 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) + .with_context(|| error_messages::TARGET_ENTRY_NOT_FOUND)? + .sources + .as_slice(), + ); + if changes { + return Ok(true); + } + } + return Ok(false); + } || remove_if_needed_from_cache_and_count_changes( + &mut cache.generated_commands.modules.system_modules, + &program_data.modules.sys_modules, + ); -impl CommandDetail { - #[inline(always)] - pub fn file_path(&self) -> PathBuf { - Path::new(&self.directory).join(&self.file) + Ok(deletions_on_cfg) } -} - -#[derive(Deserialize, Serialize, Debug, Default, Clone)] -pub struct MainCommandLineDetail { - files: Vec, - execution_result: CommandExecutionResult, - command: String, -} -/// Type alias for the underlying key-value based collection of environmental variables -pub type EnvVars = HashMap; - -#[derive(Deserialize, Serialize, Debug, Default, Clone)] -pub struct CompilersMetadata { - pub msvc: MsvcMetadata, - pub clang: ClangMetadata, - pub gcc: GccMetadata, - 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 + 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| -> bool { + scl.status.eq(&TranslationUnitStatus::ToDelete) || { + let r = user_declared_translation_units + .iter() + .any(|cc| cc.path().eq(&scl.path())); + + if !r { + log::debug!("Found translation_unit removed from cfg: {:?}", scl); + } + r + } + }; -#[derive(Deserialize, Serialize, Debug, Default, Clone)] -pub struct MsvcMetadata { - 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 - // 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, - // The environmental variables that will be injected to the underlying invoking shell - pub env_vars: EnvVars, -} + let total_cached_source_command_lines = cached_commands.len(); + cached_commands.retain(removal_conditions); -impl MsvcMetadata { - pub fn is_loaded(&self) -> bool { - self.dev_commands_prompt.is_some() && self.vs_stdlib_path.is_some() + total_cached_source_command_lines > cached_commands.len() } } - -#[derive(Deserialize, Serialize, Debug, Default, Clone)] -pub struct ClangMetadata { - pub env_vars: EnvVars, -} - -#[derive(Deserialize, Serialize, Debug, Default, Clone)] -pub struct GccMetadata { - pub env_vars: EnvVars, -} diff --git a/zork++/src/lib/cli/input/mod.rs b/zork++/src/lib/cli/input/mod.rs index 562b1899..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/commands.rs b/zork++/src/lib/cli/output/commands.rs deleted file mode 100644 index d1b5f94a..00000000 --- a/zork++/src/lib/cli/output/commands.rs +++ /dev/null @@ -1,296 +0,0 @@ -use std::collections::HashMap; -use std::fmt::Debug; -use std::slice::Iter; -use std::{ - path::{Path, PathBuf}, - process::ExitStatus, -}; - -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::{ - cache::{self, ZorkCache}, - project_model::{compiler::CppCompiler, ZorkModel}, - utils::constants, -}; -use color_eyre::{ - eyre::{eyre, Context}, - Report, Result, -}; -use serde::{Deserialize, Serialize}; - -use super::arguments::Argument; - -pub fn run_generated_commands( - program_data: &ZorkModel<'_>, - mut commands: Commands<'_>, - cache: &mut ZorkCache, - test_mode: bool, -) -> Result { - 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)?; - } - - let translation_units = commands - .pre_tasks - .iter_mut() - .chain(commands.interfaces.iter_mut()) - .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); - 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 - ); - cache::save(program_data, cache, commands, test_mode)?; - return Err(err); - } - } - } - - if !commands.main.args.is_empty() { - log::debug!("Executing the main command line..."); - - let r = execute_command(compiler, program_data, &commands.main.args, cache); - commands.main.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() { - cache::save(program_data, cache, commands, test_mode)?; - return Err(eyre!( - "Ending the program, because the main command line execution wasn't ended successfully", - )); - } - } - - cache::save(program_data, cache, commands, test_mode)?; - Ok(CommandExecutionResult::Success) -} - -/// Executes a new [`std::process::Command`] to run the generated binary -/// after the build process in the specified shell -pub fn autorun_generated_binary( - compiler: &CppCompiler, - output_dir: &Path, - executable_name: &str, -) -> Result { - let args = &[Argument::from( - output_dir - .join(compiler.as_ref()) - .join(executable_name) - .with_extension(constants::BINARY_EXTENSION), - )]; - - log::info!( - "[{compiler}] - Executing the 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(" "))), - )) -} - -/// Executes a new [`std::process::Command`] configured according the chosen -/// compiler and the current operating system -fn execute_command( - compiler: CppCompiler, - model: &ZorkModel, - arguments: &[Argument<'_>], - cache: &ZorkCache, -) -> Result { - log::trace!( - "[{compiler}] - Executing command => {:?}", - format!( - "{} {}", - compiler.get_driver(&model.compiler), - arguments.join(" ") - ) - ); - - std::process::Command::new(compiler.get_driver(&model.compiler)) - .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 -/// for some translation unit -#[derive(Debug)] -pub struct SourceCommandLine<'a> { - pub directory: PathBuf, - pub filename: String, - pub args: Arguments<'a>, - pub processed: bool, - pub execution_result: CommandExecutionResult, -} - -impl<'a> SourceCommandLine<'a> { - pub fn from_translation_unit( - tu: impl TranslationUnit, - args: Arguments<'a>, // 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, - processed, - execution_result, - } - } - - pub fn path(&self) -> PathBuf { - self.directory.join(Path::new(&self.filename)) - } -} - -#[derive(Debug)] -pub struct ExecutableCommandLine<'a> { - pub main: &'a Path, - pub sources_paths: 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(), - } - } -} - -/// Holds the generated command line arguments for a concrete compiler -#[derive(Debug)] -pub struct Commands<'a> { - 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>, -} - -impl<'a> Commands<'a> { - pub fn new(compiler: &'a CppCompiler) -> Self { - Self { - compiler: *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(), - } - } -} - -impl<'a> core::fmt::Display for Commands<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Commands for [{}]:\n- Interfaces: {:?},\n- Implementations: {:?},\n- Sources: {:?}", - self.compiler, - 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<'a>>, -) -> impl Iterator + Debug + 'a { - iter.map(|vec| { - vec.args - .iter() - .map(|e| e.value) - .collect::>() - .join(" "); - }) -} - -/// Holds a custom representation of the execution of -/// a command line in a shell. -#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq)] -pub enum CommandExecutionResult { - /// 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, - /// 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 { - fn from(value: Result) -> Self { - handle_command_execution_result(&value) - } -} - -impl From<&Result> for CommandExecutionResult { - fn from(value: &Result) -> Self { - 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 - } - } - Err(_) => CommandExecutionResult::Error, - } -} diff --git a/zork++/src/lib/cli/output/executors.rs b/zork++/src/lib/cli/output/executors.rs new file mode 100644 index 00000000..812d7db1 --- /dev/null +++ b/zork++/src/lib/cli/output/executors.rs @@ -0,0 +1,315 @@ +//! Contains helpers and data structures to be processed in a nice and neat way the commands generated to be executed +//! by Zork++ + +use std::ffi::OsStr; +use std::{path::Path, process::ExitStatus}; + +use crate::cache::EnvVars; +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<'_>, + flyweight_data: &FlyweightData, + modules_generated_commands: &mut ModulesCommands<'_>, +) -> Result<()> { + log::info!("Proceeding to execute the generated modules commands..."); + + // Process the modules + helpers::process_modules_commands(program_data, flyweight_data, modules_generated_commands) +} + +pub fn run_targets_generated_commands( + program_data: &ZorkModel<'_>, + flyweight_data: &FlyweightData, + targets: &mut IndexMap, + modules: &ModulesCommands<'_>, +) -> Result<()> { + log::info!("Proceeding to execute the generated commands..."); + + // Process the user declared targets + for (target_identifier, target_data) in targets + .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, &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, + linker_shared_args, + modules, + env_vars, + target_data, + )?; + } + + 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( + compiler: &CppCompiler, + output_dir: &Path, + executable_name: &str, +) -> Result<()> { + let args = &[Argument::from( + output_dir + .join(compiler.as_ref()) + .join(executable_name) + .with_extension(constants::BINARY_EXTENSION), + )]; + + log::info!( + "[{compiler}] - Executing the generated binary => {:?}", + 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 +/// compiler and the current operating system +fn execute_command( + model: &ZorkModel, + arguments: T, + 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!("{} {}", 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) + .args(arguments) + .envs(env_vars) + .spawn()? + .wait() + .with_context(|| format!("[{compiler}] - Command {} failed!", arguments)) +} + +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::{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; + 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<'_>, + shared_args: &Arguments<'_>, + env_vars: &HashMap, + source: &mut SourceCommandLine<'_>, + ) -> Result<()> { + let args = shared_args + .as_slice() + .iter() + .chain(source.args.as_slice().iter()) + .collect::(); + + let r = execute_command(program_data, &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, + shared_args: &[Argument], + modules: &ModulesCommands<'_>, + env_vars: &EnvVars, + target_data: &mut Target, + ) -> Result { + 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 + .cpp_stdlib + .as_slice() + .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 { + [].iter() + }) + .map(|scl| &scl.byproduct); + + let args = shared_args + .iter() + .chain(linker_args.iter()) + .chain(modules_byproducts) + .chain(linker_sources_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", + )); + } + + r + } + + pub(crate) fn process_modules_commands( + program_data: &ZorkModel<'_>, + flyweight_data: &FlyweightData, + generated_commands: &mut ModulesCommands<'_>, + ) -> Result<()> { + let translation_units_commands: Vec<&mut SourceCommandLine> = + 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."); + 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 = flyweight_data + .general_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, + flyweight_data.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(()) + } + + pub(crate) fn get_modules_translation_units_commands<'a, 'b>( + 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 + .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/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 new file mode 100644 index 00000000..84e9718d --- /dev/null +++ b/zork++/src/lib/compiler/data_factory.rs @@ -0,0 +1,188 @@ +//! 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 crate::domain::commands::arguments::{clang_args, Argument, Arguments}; +use crate::{ + cache::ZorkCache, + domain::target::ExtraArgs, + 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 +/// +/// 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<'a>(Arguments<'a>); +impl<'a> CommonArgs<'a> { + pub fn get_args(&self) -> Arguments { + self.0.clone() + } + + pub fn get_args_slice(&self) -> impl Iterator { + self.0.as_slice().iter() + } +} + +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()); + common_args.extend_from_slice(model.compiler.extra_args()); + + Self(common_args) + } +} + +impl<'a> IntoIterator for CommonArgs<'a> { + type Item = Argument<'a>; + 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, regardless the underlying chosen compiler +pub fn compiler_common_arguments_factory( + model: &ZorkModel<'_>, + cache: &ZorkCache<'_>, +) -> Box { + match model.compiler.cpp_compiler { + CppCompiler::CLANG => Box::new(ClangCommonArgs::new(model)), + CppCompiler::MSVC => Box::new(MsvcCommonArgs::new(model, cache)), + CppCompiler::GCC => Box::new(GccCommonArgs::new()), + } +} + +/// 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: std::fmt::Debug { + fn get_args(&self) -> Arguments; +} +impl Default for Box { + fn default() -> Self { + panic!("{}", error_messages::DEFAULT_OF_COMPILER_COMMON_ARGUMENTS) + } +} + +/// 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 { + fn get_args(&self) -> Arguments { + let mut args = Arguments::default(); + args.push(self.std_lib.as_arg()); + args.push(&self.implicit_modules); + args.push(&self.implicit_module_map); + args + } +} + +#[typetag::serde] +impl CompilerCommonArguments for MsvcCommonArgs { + fn get_args(&self) -> Arguments { + let mut args = Arguments::default(); + 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.push("/reference"); + args.push(format! { + "std={}", self.stdlib_ref_path.display() + }); + args.push("/reference"); + args.push(format! { + "std.compat={}", self.c_compat_stdlib_ref_path.display() + }); + args + } +} + +#[typetag::serde] +impl CompilerCommonArguments for GccCommonArgs { + fn get_args(&self) -> Arguments { + let mut args = Arguments::default(); + args.push("-fmodules-ts"); + args + } +} + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +pub struct ClangCommonArgs { + std_lib: StdLib, + implicit_modules: Cow<'static, str>, + implicit_module_map: 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.unwrap_or_default(), + implicit_modules: "-fimplicit-modules".into(), + implicit_module_map: clang_args::implicit_module_map(out_dir), + } + } +} + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +pub struct MsvcCommonArgs { + exception_handling_model: Cow<'static, str>, + no_logo: 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(model: &ZorkModel<'_>, cache: &ZorkCache<'_>) -> Self { + let out_dir: &Path = model.build.output_dir.as_ref(); + + Self { + exception_handling_model: Cow::Borrowed("/EHsc"), + no_logo: Cow::Borrowed("/nologo"), + 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 + .ccompat_stdlib_bmi_path + .clone(), + ), + } + } +} + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +pub struct GccCommonArgs { + modules_ts: Cow<'static, str>, +} +impl GccCommonArgs { + pub fn new() -> Self { + Self { + modules_ts: Cow::Borrowed("-fmodules-ts"), + } + } +} diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 99796267..aad757c7 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -1,452 +1,338 @@ //! 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. -use color_eyre::Result; +use color_eyre::eyre::{Context, ContextCompat}; 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::compiler::helpers::flag_source_file_without_changes; -use crate::project_model::compiler::StdLibMode; -use crate::utils::constants; +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::project_model::target::TargetModel; +use crate::utils::constants::error_messages; use crate::{ cache::ZorkCache, - cli::output::{arguments::Argument, commands::Commands}, + domain::translation_unit::{TranslationUnit, TranslationUnitKind}, project_model::{ - compiler::CppCompiler, + compiler::{CppCompiler, StdLibMode}, modules::{ModuleImplementationModel, ModuleInterfaceModel}, + sourceset::SourceFile, ZorkModel, }, + utils::constants, }; -/// 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>( +use self::data_factory::CommonArgs; + +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, - tests: bool, -) -> Result> { - // A registry of the generated command lines - let mut commands = Commands::new(&model.compiler.cpp_compiler); - - // Pre-tasks - if model.compiler.cpp_compiler == CppCompiler::GCC && !model.modules.sys_modules.is_empty() { - helpers::build_sys_modules(model, &mut commands, cache) - } + 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); + // 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 + generate_modular_stdlibs_cmds(model, cache); - // 1st - Build the modules - build_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 - build_executable(model, cache, &mut commands, tests)?; + // System headers as modules + if model.compiler.cpp_compiler != CppCompiler::MSVC && !model.modules.sys_modules.is_empty() { + generate_sys_modules_commands(model, cache)?; + } - Ok(commands) -} + // Generates commands for the modules + process_modules(model, cache)?; -/// 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>, -) { - let compiler = model.compiler.cpp_compiler; + // Translation units and linker + // Generate commands for the declared targets + process_targets(model, cache)?; - // TODO: remaining ones: Clang, GCC - 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_args(model, cache, StdLibMode::Cpp) - } 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(), - processed: true, - execution_result: CommandExecutionResult::Cached, - }; - source_command_line - }; - commands.pre_tasks.push(cpp_stdlib); + Ok(()) +} - let built_stdlib_path = &cache.compilers_metadata.msvc.stdlib_bmi_path; - let c_cpp_stdlib = if !built_stdlib_path.exists() { - log::trace!( - "Building the {:?} C ISO standard library implementation", - compiler - ); - msvc_args::generate_std_cmd_args(model, cache, StdLibMode::CCompat) - } 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(), - processed: true, - execution_result: CommandExecutionResult::Cached, - }; - source_command_line - }; - commands.pre_tasks.push(c_cpp_stdlib); +/// 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<'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)); + } + + if cache.generated_commands.compiler_common_args.is_none() { + cache.generated_commands.compiler_common_args = Some( + data_factory::compiler_common_arguments_factory(model, cache), + ); } } -/// 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<'_>, - cache: &ZorkCache, - commands: &'_ mut Commands<'a>, - 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) - } else { - generate_main_command_line_args(model, cache, commands, &model.executable) +/// 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>) { + // 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); } } -fn build_sources<'a>( - model: &'a ZorkModel<'_>, - cache: &ZorkCache, - commands: &'_ mut Commands<'a>, - tests: bool, +/// 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>, ) -> Result<()> { - log::info!("Building the source files..."); - let srcs = if tests { - &model.tests.sourceset.sources - } else { - &model.executable.sourceset.sources - }; + process_kind_translation_units( + model, + cache, + &model.modules.sys_modules, + TranslationUnitKind::SystemHeader, + ) + .with_context(|| error_messages::FAILURE_SYSTEM_MODULES) +} - 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); - } else { - let command_line = SourceCommandLine::from_translation_unit( - src, Arguments::default(), true, CommandExecutionResult::Cached - ); +/// 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>) -> Result<()> { + let modules = &model.modules; + + log::info!("Generating the commands for the module interfaces and partitions..."); + process_kind_translation_units( + model, + cache, + &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( + model, + cache, + &modules.implementations, + TranslationUnitKind::ModuleImplementation, + ) + .with_context(|| error_messages::FAILURE_MODULE_IMPLEMENTATIONS)?; - 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( - model.compiler.cpp_compiler, &model.build.output_dir, src - ).to_string())) - }); + Ok(()) +} + +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 + generate_linkage_targets_commands(model, cache, target)?; + } Ok(()) } -/// Triggers the build process for compile the declared modules in the project +/// 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 /// -/// 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>, +/// *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>, + target: (&'a TargetIdentifier<'a>, &'a TargetModel<'a>), ) -> Result<()> { - log::info!("Building the module interfaces and partitions..."); - build_module_interfaces(model, cache, &model.modules.interfaces, commands); - - log::info!("Building the module implementations..."); - build_module_implementations(model, cache, &model.modules.implementations, commands); - - Ok(()) + let target_identifier = target.0; + let target_data = target.1; + + log::info!( + "Generating the commands for the source files of target: {:?}", + target_identifier.name() + ); + + process_kind_translation_units( + model, + cache, + target_data.sources.as_slice(), + // names + TranslationUnitKind::SourceFile(target_identifier), + ) + .with_context(|| error_messages::TARGET_SOURCES_FAILURE) } -/// Parses the configuration in order to build the BMIs declared for the project, -/// by precompiling the module interface units -fn build_module_interfaces<'a>( +/// 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: &ZorkCache, - interfaces: &'a [ModuleInterfaceModel], - commands: &mut Commands<'a>, -) { - 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 - ); + cache: &mut ZorkCache<'a>, + target: (&'a TargetIdentifier<'a>, &'a TargetModel<'a>), +) -> Result<()> { + log::info!( + "Generating the linker command line for target: {:?}", + &target.0.name() + ); - 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( - model.compiler.cpp_compiler, &model.build.output_dir, module_interface - ))) - } - }); -} + let target_identifier = target.0; + let target_details = target.1; -/// Parses the configuration in order to compile the module implementation -/// translation units declared for the project -fn build_module_implementations<'a>( - model: &'a ZorkModel, - cache: &ZorkCache, - impls: &'a [ModuleImplementationModel], - commands: &mut Commands<'a>, -) { - 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 linker = &mut cache + .generated_commands + .targets + .get_mut(target_identifier) + .with_context(|| error_messages::TARGET_SOURCES_FAILURE)? + .linker; - 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.generated_files_paths.push(Argument::from(helpers::generate_impl_obj_file( - model.compiler.cpp_compiler, &model.build.output_dir, module_impl - ))) - } - }); -} + let compiler = &model.compiler.cpp_compiler; + let out_dir: &Path = model.build.output_dir.as_ref(); -/// 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<'a>, - target: &'a impl ExecutableTarget<'a>, -) -> Result<()> { - log::info!("Generating the main command line..."); + let target_output = Argument::from( + out_dir + .join(compiler.as_ref()) + .join(target_identifier.name()) + .with_extension(constants::BINARY_EXTENSION), + ); - let compiler = &model.compiler.cpp_compiler; - let out_dir = 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 { - 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.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!( - "{}", - out_dir - .join(compiler.as_ref()) - .join(executable_name) - .with_extension(constants::BINARY_EXTENSION) - .display() - )); - } - 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() - )); - arguments.create_and_push(format!( - "/Fe{}", - out_dir - .join(compiler.as_ref()) - .join(executable_name) - .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"); - 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(commands.generated_files_paths.clone()); + // 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, + CppCompiler::MSVC => linker.target = Argument::from(format!("/Fe{}", target_output)), + }; + } - commands.main.args.extend(arguments); - commands.main.sources_paths = target - .sourceset() - .sources - .iter() - .map(|s| s.file()) - .collect::>(); + // 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 + .extra_args + .extend_from_to_argument_slice(&target_details.extra_args); + } Ok(()) } -/// Specific operations over source files -mod sources { - use super::helpers; - use crate::bounds::ExtraArgs; - use crate::cache::ZorkCache; - use crate::cli::output::arguments::Arguments; - use crate::project_model::sourceset::SourceFile; - use crate::{ - bounds::{ExecutableTarget, TranslationUnit}, - cli::output::{ - arguments::{clang_args, Argument}, - commands::{CommandExecutionResult, Commands, SourceCommandLine}, - }, - project_model::{ - compiler::CppCompiler, - modules::{ModuleImplementationModel, ModuleInterfaceModel}, - ZorkModel, - }, - }; +/// The core procedure of the commands generation process. +/// +/// 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: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + translation_units: &'a [T], + for_kind: TranslationUnitKind<'a>, +) -> Result<()> { + for translation_unit in translation_units.iter() { + process_kind_translation_unit(model, cache, translation_unit, &for_kind)? + } - /// Generates the command line arguments for non-module source files - pub fn generate_sources_arguments<'a>( - model: &'a ZorkModel, - commands: &mut Commands<'a>, - cache: &ZorkCache, - target: &'a impl ExecutableTarget<'a>, - source: &'a SourceFile, - ) { - let compiler = model.compiler.cpp_compiler; - let out_dir = model.build.output_dir.as_ref(); + Ok(()) +} - 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()); +fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>( + model: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + translation_unit: &'a T, + for_kind: &TranslationUnitKind<'a>, +) -> Result<()> { + let compiler = model.compiler.cpp_compiler; + let lpe = cache.metadata.last_program_execution; - match compiler { - 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::add_prebuilt_module_path(compiler, out_dir)); - arguments.create_and_push("-o"); + 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()); + } + + 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 { + TranslationUnitKind::ModuleInterface => { + let resolved_tu = + 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); } - 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"), - ); + TranslationUnitKind::ModuleImplementation => { + 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) } - CppCompiler::GCC => { - arguments.create_and_push("-fmodules-ts"); - arguments.create_and_push("-o"); + TranslationUnitKind::SourceFile(related_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, related_target)?; } - }; + 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) + } + _ => (), + } + }; - let obj_file = helpers::generate_obj_file(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(source.file()); - - let command_line = SourceCommandLine::from_translation_unit( - source, - arguments, - false, - CommandExecutionResult::default(), - ); - commands.sources.push(command_line); - commands - .generated_files_paths - .push(Argument::from(obj_file.to_string())) - } + Ok(()) +} + +/// Command line arguments generators procedures for C++ standard modules +mod modules { + use std::path::{Path, PathBuf}; + + 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::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, + }; + 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_interfaces_args<'a>( - model: &'a ZorkModel, - cache: &ZorkCache, - interface: &'a ModuleInterfaceModel, - commands: &mut Commands<'a>, + pub fn generate_module_interface_cmd<'a>( + model: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + interface: &'a ModuleInterfaceModel<'a>, ) { + let mut arguments = Arguments::default(); 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()); + // 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.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!( - "-fprebuilt-module-path={}/clang/modules/interfaces", - out_dir.display() - )); + 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, @@ -454,199 +340,229 @@ mod sources { &mut arguments, ); - // 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); - // The input file - arguments.create_and_push(interface.file()); + // The generated BMI + arguments.push("-o"); + arguments.push(&binary_module_ifc); } 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.push("/ifcOutput"); 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()); - arguments.create_and_push("/ifcOutput"); - arguments.create_and_push(implicit_lookup_mius_path); + .join(dir_names::MODULES) + .join(dir_names::INTERFACES); + arguments.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}")); + 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.file()) + arguments.push("/TP"); } CppCompiler::GCC => { - arguments.create_and_push("-fmodules-ts"); - arguments.create_and_push("-x"); - arguments.create_and_push("c++"); - arguments.create_and_push("-c"); - // The input file - arguments.create_and_push(interface.file()); + arguments.push("-x"); + arguments.push("c++"); // 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); + arguments.push("-o"); + arguments.push(&binary_module_ifc); } } - let command_line = SourceCommandLine::from_translation_unit( - interface, - arguments, - false, - CommandExecutionResult::default(), - ); - commands.interfaces.push(command_line); + // The input file + arguments.push(interface.path()); + + let cmd_line = SourceCommandLine::new(interface, arguments, binary_module_ifc); + cache.generated_commands.modules.interfaces.push(cmd_line); } - /// Generates the expected arguments for compile the implementation module files - pub fn generate_module_implementation_args<'a>( - model: &'a ZorkModel, - cache: &ZorkCache, - implementation: &'a ModuleImplementationModel, - commands: &mut Commands<'a>, + /// Generates the required arguments for compile the implementation module files + pub fn generate_module_implementation_cmd<'a>( + 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(); 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()); + + // The input file + arguments.push(implementation.path()); + let obj_file_path = helpers::generate_obj_file(compiler, out_dir, implementation); 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_maps(out_dir)); - // The resultant object 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); + 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, out_dir, &mut arguments, ); - - // The input file - 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 - let obj_file_path = out_dir - .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())); - arguments.create_and_push(format!("/Fo{}", obj_file_path.display())); + arguments.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 - 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); + arguments.push("-o"); + arguments.push(&obj_file_path); } } - let command_line = SourceCommandLine::from_translation_unit( - implementation, - arguments, - false, - CommandExecutionResult::default(), + let cmd = SourceCommandLine::new(implementation.to_owned(), arguments, obj_file_path); + cache.generated_commands.modules.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` + 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( + &model.build.output_dir, + model.compiler.cpp_compiler, + sys_module_name, ); - commands.implementations.push(command_line); + + 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: PathBuf::default(), // NOTE: While we don't implement the lookup of the + // system headers + filename: sys_module.to_string(), + args, + status: TranslationUnitStatus::PendingToBuild, + byproduct: generated_bmi_path.into(), + }; + cache.generated_commands.modules.system_modules.push(cmd); + } + + 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; + 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)); + } } } -/// 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 chrono::{DateTime, Utc}; - use std::fmt::Display; +/// Specific operations over source files +mod sources { + use crate::cache::ZorkCache; + 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::{compiler::CppCompiler, ZorkModel}; + use crate::utils::constants::error_messages; + use color_eyre::eyre::{ContextCompat, Result}; + use super::helpers; + + /// Generates the command line arguments for non-module source files + pub fn generate_sources_arguments<'a>( + model: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + source: &'a SourceFile<'a>, + target_identifier: &TargetIdentifier<'a>, + ) -> Result<()> { + let compiler = model.compiler.cpp_compiler; + let out_dir = model.build.output_dir.as_ref(); + + let mut arguments = Arguments::default(); + + if compiler.eq(&CppCompiler::CLANG) { + arguments.push(clang_args::add_prebuilt_module_path(compiler, out_dir)); + } + + 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, 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(()) + } +} + +/// 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::project_model::sourceset::SourceFile; - use crate::{ - bounds::TranslationUnit, cache::ZorkCache, cli::output::commands::CommandExecutionResult, - }; + 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 @@ -657,7 +573,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 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( @@ -668,192 +584,152 @@ 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()) + temp.push_str(interface.file_stem()) } } else { - temp.push_str(interface.module_name) + temp.push_str(&interface.module_name) } temp } else { interface.module_name.to_string() }; - out_dir - .join(compiler.as_ref()) - .join("modules") - .join("interfaces") - .join(format!( - "{mod_unit}.{}", - if compiler.eq(&CppCompiler::MSVC) { - compiler.get_obj_file_extension() - } else { - compiler.get_typical_bmi_extension() - } - )) + generate_bmi_file_path(out_dir, compiler, &mod_unit) } - pub(crate) fn generate_impl_obj_file( + /// 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 { + 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 + /// `.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(implementation.file_stem()) + .join(dir_names::OBJECT_FILES) + .join::<&str>(implementation.file_stem()) .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. + /// 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 /// - /// 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, - ) { - 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 - ); + /// *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, + ) -> 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; } - let language_level = model.compiler.language_level_arg(); - let sys_modules = model - .modules - .sys_modules - .iter() - .filter(|sys_module| { - !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( - Path::new(&model.build.output_dir) - .join(model.compiler.cpp_compiler.as_ref()) - .join("modules") - .join("interfaces") - .join(sys_module) - .with_extension( - model.compiler.cpp_compiler.get_typical_bmi_extension(), - ), - )); - } - CppCompiler::GCC => { - v.push(Argument::from("-fmodules-ts")); - } - _ => {} - } + // 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); - v - }) - .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 { - commands.system_modules.insert( - // [3] is for the 4th flag pushed to v - collection_args[3].value.to_string(), - Arguments::from_vec(collection_args), - ); + if need_to_build { + TranslationUnitStatus::PendingToBuild + } else { + compute_translation_unit_status(cached_source_cmd) } } - /// 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( - compiler: &CppCompiler, - cache: &ZorkCache, - file: &Path, + /// 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: 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; + log::trace!("Module unit {:?} will be rebuilt since we've detected that you are using Clang in Windows", cached_source_cmd.path()); + return true; } - // 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 - { - log::trace!( - "File {file:?} with status: {:?}. Marked to reprocess", - cached_file.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 - } - }, + false + } + + // 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? + + /// 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, Err(e) => { - log::error!("An error happened trying to retrieve the metadata of {file:?}. Processing it anyway because {e:?}"); - false + 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 } - } else { - false } } - pub(crate) fn generate_obj_file( - compiler: CppCompiler, - out_dir: &Path, - source: &SourceFile, - ) -> impl Display { + /// 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, + } + } + + pub(crate) fn wrong_downcast_msg<'a, T: TranslationUnit<'a>>(translation_unit: &T) -> String { format!( - "{}", - out_dir - .join(compiler.as_ref()) - .join("sources") - .join(source.file_stem()) - .with_extension(compiler.get_obj_file_extension()) - .display() + "{}: {:?}", + error_messages::WRONG_DOWNCAST_FOR, + translation_unit.path() ) } } 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 fd1f7f59..2d920d08 100644 --- a/zork++/src/lib/config_file/compiler.rs +++ b/zork++/src/lib/config_file/compiler.rs @@ -1,5 +1,6 @@ //! file for represent the available configuration properties within Zork++ //! for setting up the target compiler + use serde::{Deserialize, Serialize}; use crate::project_model; @@ -8,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 /// @@ -59,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 @@ -81,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] @@ -90,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/executable.rs b/zork++/src/lib/config_file/executable.rs deleted file mode 100644 index 4c55924a..00000000 --- a/zork++/src/lib/config_file/executable.rs +++ /dev/null @@ -1,53 +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 dbc9c994..b44d0639 100644 --- a/zork++/src/lib/config_file/mod.rs +++ b/zork++/src/lib/config_file/mod.rs @@ -2,25 +2,26 @@ //! parsed data lives. pub mod build; pub mod compiler; -pub mod executable; pub mod modules; pub mod project; -pub mod tests; +pub mod target; -use std::fmt::Debug; - -use serde::{Deserialize, Serialize}; +use indexmap::IndexMap; +use serde::{Deserialize, Deserializer, Serialize}; use self::{ - build::BuildAttribute, compiler::CompilerAttribute, executable::ExecutableAttribute, - modules::ModulesAttribute, project::ProjectAttribute, tests::TestsAttribute, + build::BuildAttribute, compiler::CompilerAttribute, modules::ModulesAttribute, + project::ProjectAttribute, target::TargetAttribute, }; /// ```rust /// use zork::config_file::{ /// ZorkConfigFile, -/// compiler::CppCompiler +/// compiler::{CppCompiler, LanguageLevel}, +/// target::TargetAttribute /// }; +/// use zork::domain::target::TargetKind; +/// use indexmap::IndexMap; /// /// const CONFIG_FILE_MOCK: &str = r#" /// [project] @@ -30,14 +31,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: &IndexMap<&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,11 +87,19 @@ pub struct ZorkConfigFile<'a> { #[serde(borrow)] pub build: Option>, #[serde(borrow)] - pub executable: Option>, - #[serde(borrow)] pub modules: Option>, - #[serde(borrow)] - pub tests: Option>, + #[serde(deserialize_with = "deserialize_targets")] + pub targets: IndexMap<&'a str, TargetAttribute<'a>>, +} + +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> { diff --git a/zork++/src/lib/config_file/modules.rs b/zork++/src/lib/config_file/modules.rs index 25727d7a..2400a55a 100644 --- a/zork++/src/lib/config_file/modules.rs +++ b/zork++/src/lib/config_file/modules.rs @@ -1,14 +1,14 @@ //!! 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 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 -/// * `extra_args` - Extra arguments that will be added to the generated command lines +/// must be precompiled in order to make the importable translation units /// /// ### Tests /// @@ -24,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) @@ -59,7 +58,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>, @@ -71,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 @@ -81,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; @@ -132,7 +129,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)] @@ -151,14 +148,14 @@ 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 -#[derive(Serialize, Deserialize, Debug, PartialEq)] +/// 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)] pub module: &'a str, @@ -172,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 @@ -200,7 +197,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/config_file/project.rs b/zork++/src/lib/config_file/project.rs index edc22c1c..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 /// @@ -25,14 +25,14 @@ 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 -/// 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 @@ -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/config_file/target.rs b/zork++/src/lib/config_file/target.rs new file mode 100644 index 00000000..5a7e3b89 --- /dev/null +++ b/zork++/src/lib/config_file/target.rs @@ -0,0 +1,49 @@ +//! 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; +/// use zork::domain::target::TargetKind; +/// 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.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. +/// +/// 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/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/cli/output/arguments.rs b/zork++/src/lib/domain/commands/arguments.rs similarity index 53% rename from zork++/src/lib/cli/output/arguments.rs rename to zork++/src/lib/domain/commands/arguments.rs index cbeaf9e4..0ebf0686 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/domain/commands/arguments.rs @@ -1,35 +1,61 @@ //! 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}; use serde::{Deserialize, Serialize}; +use crate::project_model::compiler::LanguageLevel; + /// 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, +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Argument<'a>(Cow<'a, str>); + +impl<'a> Argument<'a> { + pub fn value(&self) -> &Cow<'a, str> { + &self.0 + } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } } impl<'a> From<&'a str> for Argument<'a> { fn from(value: &'a str) -> Self { - Self { value } + Self(Cow::Borrowed(value)) + } +} + +impl<'a> From<&'a String> for Argument<'a> { + fn from(value: &'a String) -> Self { + Self(Cow::Borrowed(value)) + } +} + +impl<'a> From> for Argument<'a> { + fn from(value: Cow<'a, str>) -> Self { + Self(value) + } +} + +impl<'a> From<&Cow<'a, str>> for Argument<'a> { + fn from(value: &Cow<'a, str>) -> Self { + Self(value.clone()) } } impl<'a> From for Argument<'a> { fn from(value: String) -> Argument<'a> { - Self { - value: Box::leak(value.into_boxed_str()), - } + Self(Cow::Owned(value)) } } impl<'a> From<&'a Path> for Argument<'a> { fn from(value: &'a Path) -> Self { - Self::from(format!("{}", value.display())) + Self::from(value.to_string_lossy()) } } @@ -45,35 +71,46 @@ impl<'a> From<&PathBuf> for Argument<'a> { } } -impl<'a> Deref for Argument<'a> { - type Target = &'a str; - - fn deref(&self) -> &Self::Target { - &self.value +impl<'a> From for Argument<'a> { + fn from(value: LanguageLevel) -> Self { + Self::from(value.as_ref().to_string()) } } impl<'a> Borrow for Argument<'a> { fn borrow(&self) -> &str { - self.value + &self.0 } } impl<'a> AsRef for Argument<'a> { fn as_ref(&self) -> &OsStr { - OsStr::new(self.value) + OsStr::new(self.0.as_ref()) + } +} + +impl<'a> AsRef for Argument<'a> { + fn as_ref(&self) -> &str { + &self.0 } } impl<'a> core::fmt::Display for Argument<'a> { 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)] +#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] 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)) + } +} + impl<'a> Arguments<'a> { /// Wraps an existing [`std::vec::Vec`] of [`Argument`] pub fn from_vec(vec: Vec>) -> Self { @@ -86,36 +123,52 @@ impl<'a> Arguments<'a> { } /// Creates and stores a new [`Argument`] to the end of this collection - pub fn create_and_push(&mut self, val: T) + /// from any type *T* that can be coerced into an [`Argument`] type + 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<'a>) { - 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 + /// 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`] + /// 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: &'a [Argument]) { self.0.extend_from_slice(slice); } + + /// Extends the underlying collection given a slice of any type that is convertible to [`Argument`] + /// and implements [`Clone`] + pub fn extend_from_to_argument_slice(&mut self, slice: &'a [F]) + where + F: Into> + Clone, + { + self.0.extend( + slice + .iter() + .map(|arg| >::into(arg.clone())), + ); + } + + 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<'a> Deref for Arguments<'a> { @@ -135,7 +188,36 @@ impl<'a> IntoIterator for Arguments<'a> { } } -/// Isolated module to storing custom procedures to easy create and add new command line arguments +impl<'a> IntoIterator for &Arguments<'a> { + type Item = Argument<'a>; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.clone().into_iter() + } +} + +impl<'a> FromIterator> for Arguments<'a> { + 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<'a>> for Arguments<'a> { + fn from_iter>>(iter: I) -> Arguments<'a> { + 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 pub mod clang_args { @@ -150,9 +232,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_maps<'a>(out_dir: &Path) -> Argument<'a> { + 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") @@ -161,26 +243,26 @@ 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( - dependencies: &[&str], + dependencies: &[Cow], compiler: CppCompiler, out_dir: &Path, - arguments: &mut Arguments<'_>, + arguments: &mut Arguments, ) { dependencies.iter().for_each(|ifc_dep| { arguments.push(Argument::from(format!( @@ -189,7 +271,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() ))) @@ -198,18 +280,15 @@ pub mod clang_args { } pub mod msvc_args { - use crate::{ - bounds::TranslationUnit, - cache::ZorkCache, - cli::output::commands::{CommandExecutionResult, SourceCommandLine}, - project_model::{compiler::StdLibMode, ZorkModel}, - }; + use crate::cache::ZorkCache; + use crate::domain::commands::command_lines::SourceCommandLine; + use crate::domain::translation_unit::TranslationUnit; + use crate::project_model::compiler::StdLibMode; use super::Arguments; - pub(crate) fn generate_std_cmd_args<'a>( - model: &'a ZorkModel<'_>, - cache: &ZorkCache, + pub(crate) fn generate_std_cmd<'a>( + cache: &ZorkCache<'a>, stdlib_mode: StdLibMode, ) -> SourceCommandLine<'a> { let mut arguments = Arguments::default(); @@ -217,43 +296,27 @@ 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.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"); - arguments.create_and_push(format! { - "std={}", msvc.stdlib_bmi_path.display() - }); - - arguments.create_and_push("/c"); - arguments.create_and_push(stdlib_sf.file()); - 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() }); - SourceCommandLine::from_translation_unit( - stdlib_sf, - arguments, - false, - CommandExecutionResult::default(), - ) + 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 new file mode 100644 index 00000000..c58d9e50 --- /dev/null +++ b/zork++/src/lib/domain/commands/command_lines.rs @@ -0,0 +1,110 @@ +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 indexmap::IndexMap; +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 +/// +/// * 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 +/// +/// *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, + pub filename: String, + pub args: Arguments<'a>, + pub status: TranslationUnitStatus, + pub byproduct: Argument<'a>, +} + +impl<'a> SourceCommandLine<'a> { + 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.into(), + } + } + + pub fn path(&self) -> PathBuf { + self.directory.join(Path::new(&self.filename)) + } + + pub fn filename(&self) -> &String { + &self.filename + } +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)] +pub struct LinkerCommandLine<'a> { + pub target: Argument<'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()], + } + } +} + +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_modules_command_lines( + &mut self, + ) -> impl Iterator> + Debug { + self.modules + .cpp_stdlib + .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()) + } +} 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/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 new file mode 100644 index 00000000..788b8e7d --- /dev/null +++ b/zork++/src/lib/domain/mod.rs @@ -0,0 +1,4 @@ +pub mod commands; +pub mod flyweight_data; +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..113eb449 --- /dev/null +++ b/zork++/src/lib/domain/target.rs @@ -0,0 +1,85 @@ +//! 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 sources: Vec>, + pub linker: LinkerCommandLine<'a>, + pub kind: TargetKind, + pub enabled_for_current_program_iteration: bool, +} + +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, + enabled_for_current_program_iteration: true, + } + } +} + +/// 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, 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> { + #[inline(always)] + pub fn name(&'a self) -> &'a str { + self.0.as_ref() + } +} + +/// The different types of final products +#[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, +} + +/// 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..29fe7e7d --- /dev/null +++ b/zork++/src/lib/domain/translation_unit.rs @@ -0,0 +1,155 @@ +//! 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}; +use std::borrow::Cow; +use std::fmt::{Debug, Display}; +use std::path::PathBuf; +use std::process::ExitStatus; +use transient::{Any, Inv}; + +/// 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 + } + } + }; +} + +/// The different type of translation units that `Zork++` is able to deal with +#[derive(Debug)] +pub enum TranslationUnitKind<'a> { + ModuleInterface, + ModuleImplementation, + SourceFile(&'a 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, Eq, 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 3ba671ec..ea9d5be0 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; @@ -15,35 +15,96 @@ pub mod utils; /// without having to do fancy work about checking the /// 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 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; + use std::{fs, path::Path, time::Instant}; + use crate::utils::constants::{dir_names, error_messages, ZORK}; use crate::{ cache::{self, ZorkCache}, cli::{ input::{CliArgs, Command}, - output::commands::{self, autorun_generated_binary, CommandExecutionResult, Commands}, + output::executors, }, - compiler::build_project, + compiler::generate_commands_arguments, project_model::{compiler::CppCompiler, ZorkModel}, utils::{ self, - reader::{build_model, find_config_files, ConfigFile}, + reader::{find_config_files, ConfigFile}, 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 /// 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)?; + let abs_project_root = determine_absolute_path_of_the_project_root(cli_args)?; + + // If this run is just for create a new C++ project with the given Zork++ projects creation + // by template, create it and exit + if it_is_template_creation_then_create(cli_args, &abs_project_root)? { + return Ok(()); + }; + + 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; + 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)?; // NOTE: review if we 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 = { + // 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 + }; + // 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 + // fails or abort without continuing (current behaviour) + } + + 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 it_is_template_creation_then_create( + cli_args: &CliArgs, + abs_project_root: &Path, + ) -> Result { if let Command::New { ref name, git, @@ -51,106 +112,194 @@ 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, - git, - compiler.into(), - template, - ); + create_templated_project(abs_project_root, name, git, compiler.into(), template)?; + return Ok(true); }; + Ok(false) + } - 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:?}"); + fn perform_main_work<'a>( + cli_args: &CliArgs, + program_data: &'a ZorkModel<'a>, + cache: &mut ZorkCache<'a>, + cfg_path: &Path, + ) -> Result<()> { + let generate_commands_ts = Instant::now(); - for config_file in config_files { - log::debug!( - "Launching a Zork++ work event for the configuration file: {:?}, located at: {:?}\n", - config_file.dir_entry.file_name(), - config_file.path - ); - let raw_file = fs::read_to_string(config_file.path).with_context(|| { + 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!( - "An error happened parsing the configuration file: {:?}", - config_file.dir_entry.file_name() + "{}: {:?}", + error_messages::FAILED_BUILD_FOR_CFG_FILE, + cfg_path ) - })?; + }); + + // Save the cached data for this config file + cache.save(program_data)?; - 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)?; + work_result.with_context(|| format!("Failed to complete the job for: {:?}", cfg_path)) + } - let cache = cache::load(&program_data, cli_args) - .with_context(|| "Unable to load the Zork++ cache")?; + fn do_main_work_based_on_cli_input( + cli_args: &CliArgs, + program_data: &ZorkModel<'_>, + cache: &mut ZorkCache<'_>, + ) -> Result<()> { + let compilers_metadata = &mut cache.compilers_metadata; - do_main_work_based_on_cli_input(cli_args, &program_data, cache).with_context(|| { - format!( - "Failed to build the project for the config file: {:?}", - config_file.dir_entry.file_name() - ) - })?; + 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, + &flyweight_data, + modules_generated_commands, + )?; + + let target_executed_commands = executors::run_targets_generated_commands( + program_data, + &flyweight_data, + targets_generated_commands, + modules_generated_commands, + ); + + match cli_args.command { + Command::Build => target_executed_commands, + Command::Run | Command::Test => match target_executed_commands { + Ok(_) => { + 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, + &program_data.build.output_dir, + target_identifier.name(), + )? + } + } + + return Ok(()); + } + Err(e) => Err(e), + }?, + _ => todo!("{}", error_messages::CLI_ARGS_CMD_NEW_BRANCH), } + } - Ok(()) + /// 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 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 - /// binaries, the tests declared for the projects... - fn do_main_work_based_on_cli_input<'a>( + /// 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, - program_data: &'a ZorkModel<'_>, - mut cache: ZorkCache, - ) -> Result { - let commands: Commands; + abs_project_root: &Path, + ) -> Result> { + if meta_config_file.last_time_modified > cache.metadata.last_program_execution { + cache.metadata.save_project_model = true; + let project_model = + utils::reader::build_model(zork_config_file, cli_args, abs_project_root)?; - match cli_args.command { - Command::Build => { - commands = build_project(program_data, &mut cache, false) - .with_context(|| "Failed to build project")?; + // Check for the changes made by the user on the cfg + check_for_deletions_in_cfg(&project_model, cache) + .with_context(|| error_messages::CHECK_FOR_DELETIONS)?; - commands::run_generated_commands(program_data, commands, &mut cache, 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( - &program_data.compiler.cpp_compiler, - &program_data.build.output_dir, - program_data.executable.executable_name, - ), - Err(e) => Err(e), - } - } - Command::Test => { - 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( - &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" - ), + Ok(project_model) + } else { + log::debug!("Loading the ZorkModel from the cache"); + project_model::load(cache) } } + /// 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<()> { + 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 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>, + cache: &mut ZorkCache<'a>, + cli_args: &CliArgs, + ) -> Result<()> { + for (target_identifier, target_data) in program_data.targets.iter_mut() { + // 1st - Check if there's any new target to add to the tracked ones + 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_or_disable_target_based_on_cli_inputs( + target_identifier, + target_data, + cache, + cli_args, + )?; + } + + 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); + + Ok(()) + } + /// Creates the directory for output the elements generated /// during the build process based on the client specification. /// @@ -161,30 +310,39 @@ 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...) - fn create_output_directory(model: &ZorkModel) -> Result<()> { - let out_dir = &model.build.output_dir; - let compiler = &model.compiler.cpp_compiler; - - // 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()))?; + /// 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(); + let binding = config + .build + .as_ref() + .and_then(|build_attr| build_attr.output_dir) + .unwrap_or("out"); + 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); + + 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_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))?; + 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 + // Pre Clang-18 way if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { utils::fs::create_file( &zork_intrinsics_path, @@ -202,40 +360,243 @@ 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_or_disable_target_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)?; + + 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)); + enabled + } else { + true + }; + + // 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 { + enabled + }; + + target_data.enabled_for_current_program_iteration = enabled; + cached_target.enabled_for_current_program_iteration = enabled; + + 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::{eyre::Context, Result}; + use color_eyre::Result; use tempfile::tempdir; use crate::config_file::{self, ZorkConfigFile}; - use crate::utils::{reader::build_model, template::resources::CONFIG_FILE}; + use crate::utils::constants::{dir_names, ZORK}; + + use super::{helpers, map_model_targets_to_cache}; #[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") .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 building the project model")?; - // This should create and out/ directory in the ./zork++ folder at the root of this project - super::create_output_directory(&model)?; + 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(&zcf, temp_path)?; + + 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!(zork_dir.exists()); + assert!(zork_dir.join(dir_names::CACHE).exists()); + assert!(zork_dir.join(dir_names::INTRINSICS).exists()); + + 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_or_disable_target_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()); + } - assert!(out_path.exists()); - assert!(out_path.join("clang").exists()); + let executable = cached_targets + .get(&TargetIdentifier::from("executable")) + .unwrap(); + assert!(executable.enabled_for_current_program_iteration); - assert!(out_path.join("zork").exists()); - assert!(out_path.join("zork").join("cache").exists()); - assert!(out_path.join("zork").join("intrinsics").exists()); + let tests = cached_targets + .get(&TargetIdentifier::from("tests")) + .unwrap(); + assert!(tests.enabled_for_current_program_iteration); Ok(()) } 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 9fee6de6..e28f616a 100644 --- a/zork++/src/lib/project_model/compiler.rs +++ b/zork++/src/lib/project_model/compiler.rs @@ -1,26 +1,31 @@ use core::fmt; +use std::borrow::Cow; +use crate::domain::commands::arguments::Argument; use serde::{Deserialize, Serialize}; -use crate::{bounds::ExtraArgs, cli::output::arguments::Argument}; +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: &'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 { @@ -31,7 +36,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 } } @@ -63,24 +68,39 @@ 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++", - } + }) } } - 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 { @@ -91,6 +111,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 { @@ -100,9 +128,10 @@ impl CppCompiler { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, Copy)] pub enum LanguageLevel { CPP20, + #[default] CPP23, CPP2A, CPP2B, @@ -127,12 +156,22 @@ impl AsRef for LanguageLevel { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] pub enum StdLib { STDLIBCPP, + #[default] 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()) @@ -148,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/executable.rs b/zork++/src/lib/project_model/executable.rs deleted file mode 100644 index 9d2d4a9e..00000000 --- a/zork++/src/lib/project_model/executable.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::{ - bounds::{ExecutableTarget, ExtraArgs}, - cli::output::arguments::Argument, -}; - -use super::sourceset::SourceSet; - -#[derive(Debug, PartialEq, Eq)] -pub struct ExecutableModel<'a> { - pub executable_name: &'a str, - pub sourceset: SourceSet, - pub extra_args: Vec>, -} - -impl<'a> ExtraArgs<'a> for ExecutableModel<'a> { - fn extra_args(&'a self) -> &'a [Argument<'a>] { - &self.extra_args - } -} - -impl<'a> ExecutableTarget<'a> for ExecutableModel<'a> { - fn name(&'a self) -> &'a str { - self.executable_name - } - 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 467e9046..6b675e65 100644 --- a/zork++/src/lib/project_model/mod.rs +++ b/zork++/src/lib/project_model/mod.rs @@ -1,24 +1,43 @@ pub mod build; pub mod compiler; -pub mod executable; pub mod modules; pub mod project; pub mod sourceset; -pub mod tests; +pub mod target; +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}; + +use crate::utils; + use self::{ - build::BuildModel, compiler::CompilerModel, executable::ExecutableModel, modules::ModulesModel, - project::ProjectModel, tests::TestsModel, + build::BuildModel, compiler::CompilerModel, modules::ModulesModel, project::ProjectModel, + target::TargetModel, }; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct ZorkModel<'a> { pub project: ProjectModel<'a>, pub compiler: CompilerModel<'a>, pub build: BuildModel, - pub executable: ExecutableModel<'a>, pub modules: ModulesModel<'a>, - pub tests: TestsModel<'a>, + pub targets: IndexMap, TargetModel<'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_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_messages::PROJECT_MODEL_SAVE) } diff --git a/zork++/src/lib/project_model/modules.rs b/zork++/src/lib/project_model/modules.rs index 2b2147f5..a58bfb70 100644 --- a/zork++/src/lib/project_model/modules.rs +++ b/zork++/src/lib/project_model/modules.rs @@ -1,157 +1,95 @@ use core::fmt; +use std::borrow::Cow; use std::path::{Path, PathBuf}; -use crate::bounds::ExtraArgs; -use crate::cli::output::arguments::Argument; -use crate::{bounds::TranslationUnit, config_file::modules::ModulePartition}; +use serde::{Deserialize, Serialize}; +use transient::Transient; -#[derive(Debug, PartialEq, Eq)] +use crate::config_file::modules::ModulePartition; +use crate::domain::translation_unit::TranslationUnit; +use crate::impl_translation_unit_for; + +#[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<&'a str>, - pub extra_args: Vec>, + pub sys_modules: Vec>, } -impl<'a> ExtraArgs<'a> for ModulesModel<'a> { - fn extra_args(&'a self) -> &'a [Argument<'a>] { - &self.extra_args - } -} - -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Transient, Serialize, Deserialize, Default)] pub struct ModuleInterfaceModel<'a> { pub path: PathBuf, - pub file_stem: String, - pub extension: String, - pub module_name: &'a str, + pub file_stem: Cow<'a, str>, + pub extension: Cow<'a, str>, + pub module_name: Cow<'a, str>, pub partition: Option>, - pub dependencies: Vec<&'a str>, + 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 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) - } - - 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 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) - } - - fn path(&self) -> PathBuf { - self.path.clone() - } - - fn file_stem(&self) -> String { - self.file_stem.clone() - } - - fn extension(&self) -> String { - self.extension.clone() - } -} - -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Transient, Clone, Serialize, Deserialize, Default)] 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(), + 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(), } } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Transient, Serialize, Deserialize, Default)] pub struct ModuleImplementationModel<'a> { pub path: PathBuf, - pub file_stem: String, - pub extension: String, - pub dependencies: Vec<&'a str>, + pub file_stem: Cow<'a, str>, + pub extension: Cow<'a, str>, + 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) + write!(f, "({:?}, {:?})", self.path(), self.dependencies) } } -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() - } +/// 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, Transient, Serialize, Deserialize, Default)] +pub struct SystemModule<'a> { + pub path: PathBuf, + pub file_stem: Cow<'a, str>, + pub extension: Cow<'a, str>, } -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) - } +impl_translation_unit_for!(SystemModule<'a>); - 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> fmt::Display for SystemModule<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.path()) } } diff --git a/zork++/src/lib/project_model/project.rs b/zork++/src/lib/project_model/project.rs index 4f3f0653..d358e6b8 100644 --- a/zork++/src/lib/project_model/project.rs +++ b/zork++/src/lib/project_model/project.rs @@ -1,7 +1,11 @@ -#[derive(Debug, PartialEq, Eq)] +use std::borrow::Cow; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct ProjectModel<'a> { - pub name: &'a str, - pub authors: &'a [&'a str], + pub name: Cow<'a, str>, + pub authors: Vec>, pub compilation_db: bool, - pub project_root: Option<&'a str>, + pub code_root: Option>, } diff --git a/zork++/src/lib/project_model/sourceset.rs b/zork++/src/lib/project_model/sourceset.rs index bba7313f..ce9bffc0 100644 --- a/zork++/src/lib/project_model/sourceset.rs +++ b/zork++/src/lib/project_model/sourceset.rs @@ -1,81 +1,25 @@ use core::fmt; -use std::path::{Path, PathBuf}; +use std::borrow::Cow; +use std::path::PathBuf; -use crate::bounds::TranslationUnit; +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; -// 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 -// 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() - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] -pub struct SourceFile { +#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize, Transient)] +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 { - 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) - } - - fn path(&self) -> PathBuf { - self.path.clone() - } - - fn file_stem(&self) -> String { - self.file_stem.clone() - } - - fn extension(&self) -> String { - self.extension.clone() - } -} +impl_translation_unit_for!(SourceFile<'a>); -impl TranslationUnit for &SourceFile { - 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 fmt::Display for SourceFile { +impl<'a> fmt::Display for SourceFile<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -113,14 +57,20 @@ impl GlobPattern { } } -#[derive(Debug, PartialEq, Eq)] -pub struct SourceSet { - pub sources: Vec, -} +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Clone)] +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() + } -impl SourceSet { - pub fn as_args_to(&self, dst: &mut Vec>) -> Result<()> { - let args = self.sources.iter().map(|sf| sf.file()).map(Argument::from); + pub fn as_args_to(&self, dst: &mut Vec) -> Result<()> { + let args = self.0.iter().map(|sf| sf.path()).map(Argument::from); dst.extend(args); diff --git a/zork++/src/lib/project_model/target.rs b/zork++/src/lib/project_model/target.rs new file mode 100644 index 00000000..574ebd5b --- /dev/null +++ b/zork++/src/lib/project_model/target.rs @@ -0,0 +1,14 @@ +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, Clone)] +pub struct TargetModel<'a> { + pub output_name: Cow<'a, str>, + pub sources: SourceSet<'a>, + pub extra_args: Vec>, + pub kind: TargetKind, + pub enabled_for_current_program_iteration: bool, +} diff --git a/zork++/src/lib/project_model/tests.rs b/zork++/src/lib/project_model/tests.rs deleted file mode 100644 index 336ca341..00000000 --- a/zork++/src/lib/project_model/tests.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::{ - bounds::{ExecutableTarget, ExtraArgs}, - cli::output::arguments::Argument, -}; - -use super::sourceset::SourceSet; - -#[derive(Debug, PartialEq, Eq)] -pub struct TestsModel<'a> { - pub test_executable_name: String, - pub sourceset: SourceSet, - pub extra_args: Vec>, -} - -impl<'a> ExtraArgs<'a> for TestsModel<'a> { - fn extra_args(&'a self) -> &'a [Argument<'a>] { - &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 18403c83..43d88367 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -1,8 +1,90 @@ //! 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 MODULES: &str = "modules"; + pub const INTRINSICS: &str = "intrinsics"; + pub const INTERFACES: &str = "interfaces"; + pub const IMPLEMENTATIONS: &str = "implementations"; + 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"; + 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"; + 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"; + 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"; + 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 = + "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\ + 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 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"; + 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?"; + 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"; + } +} + 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" @@ -36,21 +118,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" @@ -64,6 +140,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/fs.rs b/zork++/src/lib/utils/fs.rs index a7b838f1..f2134042 100644 --- a/zork++/src/lib/utils/fs.rs +++ b/zork++/src/lib/utils/fs.rs @@ -8,7 +8,7 @@ use std::{ }; use walkdir::WalkDir; -use super::constants; +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<()> { @@ -37,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() { @@ -85,12 +92,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, + T: Serialize + ?Sized, { serde_json::to_writer_pretty( - File::create(path).with_context(|| "Error creating the cache file")?, + File::create(path).with_context(|| format!("Error opening file: {:?}", path))?, data, ) .with_context(|| "Error serializing data to the cache") @@ -99,11 +106,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().join(constants::ZORK_CACHE_FILENAME)) - .with_context(|| "Error opening the cache file")?, + File::open(path.as_ref()).with_context(|| format!("Error opening {:?}", path))?, ); + Ok(serde_json::from_reader(buffer).unwrap_or_default()) } diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 8c36d52f..81a10519 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -1,35 +1,39 @@ 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::{ - cli::output::arguments::Argument, 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::path::{Path, PathBuf}; -use walkdir::{DirEntry, WalkDir}; +use walkdir::WalkDir; -use super::constants::DEFAULT_OUTPUT_DIR; +use super::constants::dir_names; /// Details about a found configuration file on the project /// @@ -37,14 +41,14 @@ use super::constants::DEFAULT_OUTPUT_DIR; /// 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 /// 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 `.` @@ -52,16 +56,16 @@ 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, // TODO: this shoudn't be necessary + filename_match: &Option, ) -> Result> { log::debug!("Searching for Zork++ configuration files..."); let mut files = vec![]; for e in WalkDir::new(base_path) - .max_depth(2) + .max_depth(2) // TODO:(Workspaces) so, max_depth should be zero when the cfg arg is ready .into_iter() .filter_map(|e| e.ok()) { @@ -76,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()?), }) } } @@ -90,59 +94,68 @@ 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 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 code_root = PathBuf::from(absolute_project_root).join( + project + .code_root + .as_ref() + .map(|pr| pr.to_string()) + .unwrap_or_default(), + ); - 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 modules = assemble_modules_model(config.modules, &code_root); + let targets = assemble_targets_model(config.targets, proj_name, &code_root); Ok(ZorkModel { project, compiler, build, - executable, modules, - tests, + targets, }) } -fn assemble_project_model<'a>(config: &'a ProjectAttribute) -> 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 &[&str], |auths| auths.as_slice()), + .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, + code_root: config.code_root.map(Cow::Borrowed), } } fn assemble_compiler_model<'a>( - config: &'a CompilerAttribute, + config: CompilerAttribute<'a>, 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(Argument::from).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() + cpp_compiler: config.cpp_compiler.into(), + 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()) }, cpp_standard: config.cpp_standard.clone().into(), std_lib: config.std_lib.clone().map(|lib| lib.into()), @@ -150,132 +163,112 @@ 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) .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), } } -//noinspection ALL -fn assemble_executable_model<'a>( - project_name: &'a str, - config: &'a 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); - - let sources = config - .and_then(|exe| exe.sources.clone()) - .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: &'a Option, - project_root: &Path, + config: Option>, + code_root: &Path, ) -> ModulesModel<'a> { - let config = config.as_ref(); + let modules = config.unwrap_or_default(); - let base_ifcs_dir = config - .and_then(|modules| modules.base_ifcs_dir) - .unwrap_or("."); + let base_ifcs_dir = modules + .base_ifcs_dir + .map(Path::new) + .map(Cow::from) + .unwrap_or_default(); - let interfaces = config - .and_then(|modules| modules.interfaces.as_ref()) + let interfaces = modules + .interfaces .map(|ifcs| { - ifcs.iter() - .map(|m_ifc| 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, code_root) + }) .collect() }) .unwrap_or_default(); - let base_impls_dir = config - .and_then(|modules| modules.base_impls_dir) - .unwrap_or("."); + let base_impls_dir = modules + .base_impls_dir + .map(Path::new) + .map(Cow::from) + .unwrap_or_default(); - 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, code_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 extra_args = config - .and_then(|mod_attr| mod_attr.extra_args.as_ref()) - .map(|args| args.iter().map(|arg| Argument::from(*arg)).collect()) - .unwrap_or_default(); + let sys_modules = modules + .sys_modules + .as_ref() + .map_or_else(Default::default, |headers| { + headers + .iter() + .map(|sys_header| SystemModule { + file_stem: Cow::from(*sys_header), + ..Default::default() + }) + .collect() + }); ModulesModel { - base_ifcs_dir: Path::new(base_ifcs_dir), + 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, - project_root: &Path, + config: ModuleInterface<'a>, + base_path: &Path, + code_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(|| { - Path::new(config.file) - .file_stem() - .unwrap_or_else(|| panic!("Found ill-formed path on: {}", config.file)) - .to_str() - .unwrap() - }); + let cfg_file = config.file; + + let file_path = Path::new(code_root).join(base_path).join(cfg_file); - let dependencies = config.dependencies.clone().unwrap_or_default(); - let partition = if config.partition.is_none() { - None + let module_name = if let Some(mod_name) = config.module_name { + Cow::Borrowed(mod_name) } else { - Some(ModulePartitionModel::from( - config.partition.as_ref().unwrap(), - )) + 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 = 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:?}") }); + 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, @@ -283,19 +276,26 @@ fn assemble_module_interface_model<'a>( } fn assemble_module_implementation_model<'a>( - config: &'a ModuleImplementation, - base_path: &str, - project_root: &Path, + config: ModuleImplementation<'a>, + base_path: &Path, + code_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() + .into_iter() + .map(Cow::Borrowed) + .collect::>>(); + + 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 { let implicit_dependency = config.file.split_at(idx); - dependencies.push(implicit_dependency.0) + dependencies.push(Cow::Owned(implicit_dependency.0.to_owned())) } else { - dependencies.push(config.file); + dependencies.push(Cow::Borrowed(config.file)); } } @@ -305,46 +305,64 @@ 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>( +fn assemble_targets_model<'a>( + targets: IndexMap<&'a str, TargetAttribute<'a>>, project_name: &'a str, - config: &'a 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(), - ); + code_root: &Path, +) -> IndexMap, TargetModel<'a>> { + targets + .into_iter() + .map(|(k, v)| { + ( + TargetIdentifier(Cow::Borrowed(k)), + assemble_target_model(v, project_name, code_root), + ) + }) + .collect() +} - let sources = config - .and_then(|exe| exe.sources.clone()) - .unwrap_or_default(); - let sourceset = get_sourceset_for(sources, project_root); +fn assemble_target_model<'a>( + target_config: TargetAttribute<'a>, + project_name: &'a str, + code_root: &Path, +) -> TargetModel<'a> { + let sources = target_config + .sources + .into_iter() + .map(Cow::Borrowed) + .collect(); - let extra_args = config - .and_then(|test| test.extra_args.as_ref()) + let sources = get_sources_for_target(sources, code_root); + + let extra_args = target_config + .extra_args .map(|args| args.iter().map(|arg| Argument::from(*arg)).collect()) .unwrap_or_default(); - TestsModel { - 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(), + enabled_for_current_program_iteration: true, // NOTE: For now, it can only be manually + // disabled by cli args } } -fn get_sourceset_for(srcs: Vec<&str>, project_root: &Path) -> SourceSet { +/// 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>, code_root: &Path) -> SourceSet<'a> { let sources = srcs .iter() .map(|src| { - let target_src = project_root.join(src); + let target_src = code_root.join(src.as_ref()); if src.contains('*') { Source::Glob(GlobPattern(target_src)) } else { @@ -362,18 +380,21 @@ fn get_sourceset_for(srcs: Vec<&str>, 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(); - SourceSet { sources } + SourceSet::new(sources) } #[cfg(test)] 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}, @@ -383,83 +404,54 @@ 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)?; - 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++", - authors: &["zerodaycode.gz@gmail.com"], - compilation_db: false, - project_root: None, - }, - compiler: CompilerModel { - cpp_compiler: CppCompiler::CLANG, - driver_path: "", - cpp_standard: LanguageLevel::CPP20, - std_lib: None, - extra_args: vec![], - }, - build: BuildModel { - output_dir: abs_path_for_mock.join("out"), - }, - executable: ExecutableModel { - executable_name: "Zork++", - sourceset: SourceSet { sources: vec![] }, - extra_args: vec![], - }, - 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".to_string(), - sourceset: SourceSet { sources: vec![] }, - extra_args: vec![], - }, - }; - - 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 model = build_model(config, &cli_args, &abs_path_for_mock); + + let mut targets = IndexMap::new(); + targets.insert( + TargetIdentifier::from("executable"), + TargetModel { + output_name: "zork".into(), + 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, + }, + ); + targets.insert( + TargetIdentifier::from("tests"), + TargetModel { + output_name: "zork_tests".into(), + 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, + }, + ); let expected = ZorkModel { project: ProjectModel { - name: "Zork++", - authors: &["zerodaycode.gz@gmail.com"], + 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, - driver_path: "", + driver_path: Cow::Borrowed(""), cpp_standard: LanguageLevel::CPP2B, std_lib: Some(StdLib::LIBCPP), extra_args: vec![Argument::from("-Wall")], @@ -467,54 +459,47 @@ mod test { build: BuildModel { output_dir: abs_path_for_mock.clone(), }, - executable: ExecutableModel { - executable_name: "zork", - sourceset: SourceSet { sources: vec![] }, - 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"), - file_stem: String::from("maths"), - extension: String::from("cppm"), - module_name: "maths", + 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"), - module_name: "maths", + file_stem: Cow::Borrowed("some_module"), + extension: Cow::Borrowed("cppm"), + module_name: "maths".into(), partition: None, 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"), - file_stem: String::from("maths"), - extension: String::from("cpp"), - dependencies: vec!["maths"], + 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"), - dependencies: vec!["iostream"], + file_stem: Cow::from("some_module_impl"), + extension: Cow::from("cpp"), + dependencies: vec!["iostream".into()], }, ], - sys_modules: vec!["iostream"], - extra_args: vec![Argument::from("-Wall")], - }, - tests: TestsModel { - test_executable_name: "zork_check".to_string(), - sourceset: SourceSet { sources: vec![] }, - extra_args: vec![Argument::from("-pedantic")], + sys_modules: vec![SystemModule { + file_stem: Cow::Borrowed("iostream"), + ..Default::default() + }], }, + targets, }; assert_eq!(model.unwrap(), expected); diff --git a/zork++/src/lib/utils/template/mod.rs b/zork++/src/lib/utils/template/mod.rs index 89a7eb0e..99828b2b 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 @@ -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 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] 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] diff --git a/zork++/test/test.rs b/zork++/test/test.rs index 262de897..fa6ebfd4 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()); @@ -114,7 +114,6 @@ fn test_msvc_full_process() -> Result<()> { #[cfg(target_os = "windows")] #[test] -#[ignore] fn test_gcc_windows_full_process() -> Result<()> { let project_name = "gcc_example"; @@ -220,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