diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index a7ee5aa5..e1ff22ad 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -2,7 +2,7 @@ name: Code quality and sanity on: push: - branches: '*' + branches: 'main' pull_request: branches: '*' @@ -22,12 +22,12 @@ jobs: - uses: hecrj/setup-rust-action@v1 with: components: clippy - - - name: Verifiying the code quality with Clippy + + - name: Verifiying the code quality with Clippy + working-directory: zork++ run: | - cd zork++ cargo clippy --all-targets --all-features - + rustfmt: name: Verify code formatting runs-on: ubuntu-latest @@ -36,51 +36,31 @@ jobs: - name: Caching project dependencies id: project-cache uses: Swatinem/rust-cache@v2 - - uses: hecrj/setup-rust-action@v1 + + - uses: hecrj/setup-rust-action@v2 with: components: rustfmt + - name: Checking the format sanity of the project + working-directory: zork++ run: | - cd zork++ cargo fmt --all -- --check - tests: - name: Run the tests - Clang + unit-and-doc-tests: + name: Verify code formatting runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - clang_version: [15, 16, 17] steps: - uses: actions/checkout@v3 - name: Caching project dependencies id: project-cache uses: Swatinem/rust-cache@v2 - - name: Install Clang ${{ matrix.clang_version }} - run: | - # Exit on error - set -e - # Download and execute the LLVM installation script for the specified Clang version - echo "-----> Downloading and executing the LLVM installation script for Clang ${{ matrix.clang_version }}" - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo ./llvm.sh ${{ matrix.clang_version }} - - echo "-----> Installing libc++" - sudo apt-get install -y libc++-${{ matrix.clang_version }}-dev libc++abi-${{ matrix.clang_version }}-dev libunwind-${{ matrix.clang_version }} libunwind-${{ matrix.clang_version }}-dev libc6 libzstd1 - # Update the symbolic link to point to the newly installed Clang version - echo "-----> Updating the symbolic link to point to Clang ${{ matrix.clang_version }}" - sudo rm -f /usr/bin/clang++ - sudo ln -s /usr/bin/clang++-${{ matrix.clang_version }} /usr/bin/clang++ - + - uses: hecrj/setup-rust-action@v2 + with: + components: rustfmt - # Display the installation directory and version of the installed Clang - echo "-----> Clang-${{ matrix.clang_version }} was installed on:" - which clang-${{ matrix.clang_version }} - echo "-----> Clang++ was installed on:" - which clang++-${{ matrix.clang_version }} - - name: Running the tests for the project + - name: Run Unit and Doc tests + working-directory: zork++ run: | - cd zork++ - RUST_LOG=trace cargo test 2>&1 --all --color=always --no-fail-fast -- --nocapture --show-output --test-threads=1 + cargo test --workspace --lib --color=always --no-fail-fast + cargo test --workspace --doc --color=always --no-fail-fast diff --git a/.github/workflows/tests-clang-linux.yml b/.github/workflows/tests-clang-linux.yml new file mode 100644 index 00000000..b2bf910e --- /dev/null +++ b/.github/workflows/tests-clang-linux.yml @@ -0,0 +1,52 @@ +name: Clang Tests For Linux builds + +on: + push: + branches: 'main' + pull_request: + branches: '*' + +jobs: + tests: + name: Run the tests - Clang + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + clang_version: [15, 16, 17, 18] + steps: + - uses: actions/checkout@v3 + - name: Caching project dependencies + id: project-cache + uses: Swatinem/rust-cache@v2 + + - if: ${{ matrix.clang_version < 18 }} + name: Install Clang ${{ matrix.clang_version }} + run: | + # Exit on error + set -e + # Download and execute the LLVM installation script for the specified Clang version + echo "-----> Downloading and executing the LLVM installation script for Clang ${{ matrix.clang_version }}" + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh ${{ matrix.clang_version }} + + echo "-----> Installing libc++" + sudo apt-get install -y libc++-${{ matrix.clang_version }}-dev libc++abi-${{ matrix.clang_version }}-dev libunwind-${{ matrix.clang_version }} libunwind-${{ matrix.clang_version }}-dev libc6 libzstd1 + + # Update the symbolic link to point to the newly installed Clang version + echo "-----> Updating the symbolic link to point to Clang ${{ matrix.clang_version }}" + sudo rm -f /usr/bin/clang++ + sudo ln -s /usr/bin/clang++-${{ matrix.clang_version }} /usr/bin/clang++ + + # Display the installation directory and version of the installed Clang + echo "-----> Clang-${{ matrix.clang_version }} was installed on:" + which clang-${{ matrix.clang_version }} + echo "-----> Clang++ was installed on:" + which clang++-${{ matrix.clang_version }} + + - if: ${{ matrix.clang_version < 18 }} + name: Running the tests for the project + run: | + cd zork++ + RUST_LOG=trace cargo test 2>&1 clang --color=always --no-fail-fast -- --nocapture --show-output --test-threads=1 diff --git a/.github/workflows/tests-gcc.yml b/.github/workflows/tests-gcc.yml new file mode 100644 index 00000000..64d78d47 --- /dev/null +++ b/.github/workflows/tests-gcc.yml @@ -0,0 +1,25 @@ +name: GCC Tests For Linux builds + +on: + push: + branches: 'main' + pull_request: + branches: '*' + +jobs: + tests: + name: Run the tests - GCC + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v3 + + - 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 --color=always --no-fail-fast -- --nocapture --show-output --test-threads=1 diff --git a/.github/workflows/tests-msvc.yml b/.github/workflows/tests-msvc.yml new file mode 100644 index 00000000..842109fd --- /dev/null +++ b/.github/workflows/tests-msvc.yml @@ -0,0 +1,30 @@ +name: MSVC Tests For Windows builds + +on: + push: + branches: 'main' + pull_request: + branches: '*' + +jobs: + tests: + name: Run the tests - MSVC + runs-on: windows-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v3 + + - name: Caching project dependencies + id: project-cache + uses: Swatinem/rust-cache@v2 + + - name: Setting up Rust + uses: hecrj/setup-rust-action@v1 + with: + rust-version: stable + + - name: Running the tests for the project + run: | + cd zork++ + cargo test 2>&1 msvc --color=always --no-fail-fast -- --nocapture --show-output --test-threads=1 diff --git a/release-config/windows-installer-zork.iss b/release-config/windows-installer-zork.iss index 21a066d2..902a966b 100644 --- a/release-config/windows-installer-zork.iss +++ b/release-config/windows-installer-zork.iss @@ -1,76 +1,76 @@ -; Script generated by the Inno Setup Script Wizard. -; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! - -#define MyAppName "Zork++" -#define MyAppVersion "0.8.8" -#define MyAppPublisher "Zero Day Code" -#define MyAppURL "https://github.com/zerodaycode/Zork" -#define MyAppExeName "zork++.exe" -#define MyAppAssocName MyAppName + " File" -#define MyAppAssocExt ".myp" -#define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt - -[Setup] -; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. -; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) -AppId={{428903B8-CFCC-4E80-AD1D-6BAE9EAEC38C} -AppName={#MyAppName} -AppVersion={#MyAppVersion} -;AppVerName={#MyAppName} {#MyAppVersion} -AppPublisher={#MyAppPublisher} -AppPublisherURL={#MyAppURL} -AppSupportURL={#MyAppURL} -AppUpdatesURL={#MyAppURL} -DefaultDirName={autopf}\{#MyAppName} -ChangesAssociations=yes -DefaultGroupName={#MyAppName} -DisableProgramGroupPage=yes -OutputDir=..\ -OutputBaseFilename=zork-installer -Compression=lzma -SolidCompression=yes -WizardStyle=modern -ChangesEnvironment=true -SetupIconFile=..\assets\installer_logo.ico - - -[Languages] -Name: "english"; MessagesFile: "compiler:Default.isl" -Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" - -[Files] -Source: "..\LICENSE"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\zork++\target\release\zork++.exe"; DestDir: "{app}"; Flags: ignoreversion - -; NOTE: Don't use "Flags: ignoreversion" on any shared system files - -[Registry] -Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue -Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey -Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0" -Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1""" -Root: HKA; Subkey: "Software\Classes\Applications\{#MyAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".myp"; ValueData: "" -Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}";Check: NeedsAddPath('{app}') -[Icons] -Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" - -[Run] -Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent - -[Code] - -function NeedsAddPath(Param: string): boolean; -var - OrigPath: string; -begin - if not RegQueryStringValue(HKEY_LOCAL_MACHINE, - 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', - 'Path', OrigPath) - then begin - Result := True; - exit; - end; - { look for the path with leading and trailing semicolon } - { Pos() returns 0 if not found } - Result := Pos(';' + Param + ';', ';' + OrigPath + ';') = 0; +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define MyAppName "Zork++" +#define MyAppVersion "0.9.0" +#define MyAppPublisher "Zero Day Code" +#define MyAppURL "https://github.com/zerodaycode/Zork" +#define MyAppExeName "zork++.exe" +#define MyAppAssocName MyAppName + " File" +#define MyAppAssocExt ".myp" +#define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{428903B8-CFCC-4E80-AD1D-6BAE9EAEC38C} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +;AppVerName={#MyAppName} {#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={autopf}\{#MyAppName} +ChangesAssociations=yes +DefaultGroupName={#MyAppName} +DisableProgramGroupPage=yes +OutputDir=..\ +OutputBaseFilename=zork-installer +Compression=lzma +SolidCompression=yes +WizardStyle=modern +ChangesEnvironment=true +SetupIconFile=..\assets\installer_logo.ico + + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" + +[Files] +Source: "..\LICENSE"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\zork++\target\release\zork++.exe"; DestDir: "{app}"; Flags: ignoreversion + +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Registry] +Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey +Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0" +Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1""" +Root: HKA; Subkey: "Software\Classes\Applications\{#MyAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".myp"; ValueData: "" +Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}";Check: NeedsAddPath('{app}') +[Icons] +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent + +[Code] + +function NeedsAddPath(Param: string): boolean; +var + OrigPath: string; +begin + if not RegQueryStringValue(HKEY_LOCAL_MACHINE, + 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', + 'Path', OrigPath) + then begin + Result := True; + exit; + end; + { look for the path with leading and trailing semicolon } + { Pos() returns 0 if not found } + Result := Pos(';' + Param + ';', ';' + OrigPath + ';') = 0; end; \ No newline at end of file diff --git a/rust-analyzer.json b/rust-analyzer.json new file mode 100644 index 00000000..7ced450e --- /dev/null +++ b/rust-analyzer.json @@ -0,0 +1,6 @@ +{ + + "procMacro": { + "enable": true + } +} diff --git a/workflows/code-coverage.yml b/workflows/code-coverage.yml new file mode 100644 index 00000000..7ee940a5 --- /dev/null +++ b/workflows/code-coverage.yml @@ -0,0 +1,64 @@ +name: Code Coverage + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+' + +env: + CARGO_TERM_COLOR: always + +jobs: + code-coverage: + permissions: + contents: write + env: + CARGO_INCREMENTAL: '0' + RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' + RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Use nightly toolchain + run: | + rustup toolchain install nightly + rustup override set nightly + + - name: Caching cargo dependencies + id: project-cache + uses: Swatinem/rust-cache@v2 + + - if: ${{ steps.cache-cargo.outputs.cache-hit != 'true' }} + name: Install grcov + run: cargo install grcov + + - name: Make the USER own the working directory + if: ${{ matrix.os == 'ubuntu-latest' }} + run: sudo chown -R $USER:$USER ${{ github.workspace }} + + - name: Run tests + working-directory: zork++ + run: | + cargo test --all-features --no-fail-fast --target=x86_64-unknown-linux-gnu -- --show-output --test-threads=1 + + - name: Generate code coverage report + working-directory: zork++ + run: | + grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing -o ./target/debug/coverage + grcov . -s . --binary-path ./target/debug/ -t cobertura --branch --ignore-not-existing -o ./target/debug/coverage/code_cov.xml + + - name: Publish Test Results + uses: actions/upload-artifact@v3 + with: + name: Unit Test Results + path: | + ./zork++/target/debug/coverage/code_cov.xml + ./zork++/target/debug/coverage/index.html + + - name: Publish coverage report to GitHub Pages + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: ./zork++/target/debug/coverage + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/workflows/code-quality.yml b/workflows/code-quality.yml new file mode 100644 index 00000000..c6fd64b3 --- /dev/null +++ b/workflows/code-quality.yml @@ -0,0 +1,52 @@ +name: Code quality and sanity + +on: + push: + branches: 'main' + pull_request: + branches: '*' + +jobs: + clippy: + name: Lint with Clippy + runs-on: ubuntu-latest + env: + RUSTFLAGS: -Dwarnings + steps: + - uses: actions/checkout@v3 + + - name: Caching project dependencies + id: project-cache + uses: Swatinem/rust-cache@v2 + + - uses: hecrj/setup-rust-action@v1 + with: + components: clippy + + - name: Verifiying the code quality with Clippy + run: | + cd zork++ + cargo clippy --all-targets --all-features + + rustfmt: + name: Verify code formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Caching project dependencies + id: project-cache + uses: Swatinem/rust-cache@v2 + + - uses: hecrj/setup-rust-action@v2 + with: + components: rustfmt + + - name: Checking the format sanity of the project + run: | + cd zork++ + cargo fmt --all -- --check + + - name: Run Unit and Doc tests + run: | + cargo test --workspace --lib --color=always --no-fail-fast + cargo test --workspace --doc --color=always --no-fail-fast diff --git a/workflows/release.yml b/workflows/release.yml new file mode 100644 index 00000000..056c7f03 --- /dev/null +++ b/workflows/release.yml @@ -0,0 +1,119 @@ +name: Zork++ Release + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+' + +jobs: + generate-release: + permissions: + contents: write + runs-on: 'ubuntu-latest' + + steps: + - name: Generate release ${{ steps.version.outputs.version }} + uses: actions/create-release@v1 + id: create-release + with: + draft: false + prerelease: false + release_name: ${{ steps.version.outputs.version }} + tag_name: ${{ github.ref }} + env: + GITHUB_TOKEN: ${{ github.token }} + outputs: + upload_url: ${{ steps.create-release.outputs.upload_url }} + + generate-binaries: + needs: 'generate-release' + permissions: + contents: write + strategy: + matrix: + os: ['ubuntu-latest', 'windows-latest'] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + + - name: Caching cargo dependencies + id: project-cache + uses: Swatinem/rust-cache@v2 + + - name: Make Rust environment + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: Building Zork's exe + working-directory: ./zork++ + run: | + cargo build --release + + - name: Install cargo-deb + run: | + cargo install cargo-deb + + - name: Generate .deb package for Linux + if: ${{ matrix.os == 'ubuntu-latest' }} + working-directory: ./zork++ + run: | + cargo deb --no-build --output ./target/debian/zork.deb + mv ./target/debian/zork.deb ./target/debian/zork++.deb + + - name: Building the installer with Inno Setup + if: ${{ matrix.os == 'windows-latest' }} + run: | + rename ".\zork++\target\release\zork.exe" "zork++.exe" + "%programfiles(x86)%\Inno Setup 6\iscc.exe" ".\release-config\windows-installer-zork.iss" + shell: cmd + + - name: Make Zip for Windows + if: ${{ matrix.os == 'windows-latest' }} + run: | + 7z a windows-installer.zip zork-installer.exe + + - name: Generate compressed tar archive for Linux + if: ${{ matrix.os == 'ubuntu-latest' }} + working-directory: ./zork++ + run: | + mv ./target/release/zork ./target/release/zork++ + tar -czvf zork++.tar.gz ./target/release/zork++ ../LICENSE + + - name: Upload Windows artifact + if: ${{ matrix.os == 'windows-latest' }} + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ needs.generate-release.outputs.upload_url }} + asset_path: windows-installer.zip + asset_name: windows-installer.zip + asset_content_type: application/zip + + - name: Upload Linux tar archive + if: ${{ matrix.os == 'ubuntu-latest' }} + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ needs.generate-release.outputs.upload_url }} + asset_path: ./zork++/zork++.tar.gz + asset_name: ./zork++/zork++.tar.gz + asset_content_type: application/tar+gzip + + - name: Upload Linux .deb package + if: ${{ matrix.os == 'ubuntu-latest' }} + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ needs.generate-release.outputs.upload_url }} + asset_path: zork++/target/debian/zork++.deb + asset_name: zork++.deb + asset_content_type: application/vnd.debian.binary-package diff --git a/workflows/tests-clang-linux.yml b/workflows/tests-clang-linux.yml new file mode 100644 index 00000000..b2bf910e --- /dev/null +++ b/workflows/tests-clang-linux.yml @@ -0,0 +1,52 @@ +name: Clang Tests For Linux builds + +on: + push: + branches: 'main' + pull_request: + branches: '*' + +jobs: + tests: + name: Run the tests - Clang + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + clang_version: [15, 16, 17, 18] + steps: + - uses: actions/checkout@v3 + - name: Caching project dependencies + id: project-cache + uses: Swatinem/rust-cache@v2 + + - if: ${{ matrix.clang_version < 18 }} + name: Install Clang ${{ matrix.clang_version }} + run: | + # Exit on error + set -e + # Download and execute the LLVM installation script for the specified Clang version + echo "-----> Downloading and executing the LLVM installation script for Clang ${{ matrix.clang_version }}" + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh ${{ matrix.clang_version }} + + echo "-----> Installing libc++" + sudo apt-get install -y libc++-${{ matrix.clang_version }}-dev libc++abi-${{ matrix.clang_version }}-dev libunwind-${{ matrix.clang_version }} libunwind-${{ matrix.clang_version }}-dev libc6 libzstd1 + + # Update the symbolic link to point to the newly installed Clang version + echo "-----> Updating the symbolic link to point to Clang ${{ matrix.clang_version }}" + sudo rm -f /usr/bin/clang++ + sudo ln -s /usr/bin/clang++-${{ matrix.clang_version }} /usr/bin/clang++ + + # Display the installation directory and version of the installed Clang + echo "-----> Clang-${{ matrix.clang_version }} was installed on:" + which clang-${{ matrix.clang_version }} + echo "-----> Clang++ was installed on:" + which clang++-${{ matrix.clang_version }} + + - if: ${{ matrix.clang_version < 18 }} + name: Running the tests for the project + run: | + cd zork++ + RUST_LOG=trace cargo test 2>&1 clang --color=always --no-fail-fast -- --nocapture --show-output --test-threads=1 diff --git a/workflows/tests-gcc.yml b/workflows/tests-gcc.yml new file mode 100644 index 00000000..64d78d47 --- /dev/null +++ b/workflows/tests-gcc.yml @@ -0,0 +1,25 @@ +name: GCC Tests For Linux builds + +on: + push: + branches: 'main' + pull_request: + branches: '*' + +jobs: + tests: + name: Run the tests - GCC + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v3 + + - 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 --color=always --no-fail-fast -- --nocapture --show-output --test-threads=1 diff --git a/workflows/tests-msvc.yml b/workflows/tests-msvc.yml new file mode 100644 index 00000000..842109fd --- /dev/null +++ b/workflows/tests-msvc.yml @@ -0,0 +1,30 @@ +name: MSVC Tests For Windows builds + +on: + push: + branches: 'main' + pull_request: + branches: '*' + +jobs: + tests: + name: Run the tests - MSVC + runs-on: windows-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v3 + + - name: Caching project dependencies + id: project-cache + uses: Swatinem/rust-cache@v2 + + - name: Setting up Rust + uses: hecrj/setup-rust-action@v1 + with: + rust-version: stable + + - name: Running the tests for the project + run: | + cd zork++ + cargo test 2>&1 msvc --color=always --no-fail-fast -- --nocapture --show-output --test-threads=1 diff --git a/zork++/Cargo.lock b/zork++/Cargo.lock index f7bf6739..ce34500a 100644 --- a/zork++/Cargo.lock +++ b/zork++/Cargo.lock @@ -49,47 +49,48 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys", @@ -101,16 +102,16 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" @@ -153,9 +154,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.94" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" [[package]] name = "cfg-if" @@ -165,9 +166,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -281,9 +282,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "core-foundation-sys" @@ -348,9 +349,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -360,28 +361,38 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] [[package]] name = "env_logger" -version = "0.10.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ + "anstream", + "anstyle", + "env_filter", "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys", @@ -399,9 +410,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "gimli" @@ -446,12 +457,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "humantime" version = "2.1.0" @@ -498,15 +503,10 @@ dependencies = [ ] [[package]] -name = "is-terminal" -version = "0.4.12" +name = "is_terminal_polyfill" +version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys", -] +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itertools" @@ -540,15 +540,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" @@ -564,18 +564,18 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -615,9 +615,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" dependencies = [ "num-traits", "plotters-backend", @@ -628,24 +628,24 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" dependencies = [ "plotters-backend", ] [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" dependencies = [ "unicode-ident", ] @@ -710,15 +710,15 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", @@ -729,9 +729,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -744,18 +744,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.197" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", @@ -764,9 +764,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -781,9 +781,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.58" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -802,15 +802,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "textwrap" version = "0.16.1" @@ -940,11 +931,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys", ] [[package]] @@ -1037,7 +1028,7 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "zork" -version = "0.8.8" +version = "0.9.0" dependencies = [ "chrono", "clap 4.5.4", @@ -1046,6 +1037,7 @@ dependencies = [ "env_logger", "glob", "log", + "regex", "serde", "serde_json", "tempfile", diff --git a/zork++/Cargo.toml b/zork++/Cargo.toml index abc5856b..2b599124 100644 --- a/zork++/Cargo.toml +++ b/zork++/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zork" -version = "0.8.8" +version = "0.9.0" authors = ["Zero Day Code"] edition = "2021" description = "A modern C++ project manager and build system for modern C++" @@ -15,16 +15,17 @@ name = "zork" path = "src/bin/main.rs" [dependencies] -toml = "0.5.10" +toml = "0.5.11" glob = "0.3.1" -serde = { version = "1.0.138", features = ["derive"] } +serde = { version = "1.0.202", features = ["derive"] } clap = { version = "4.0.32", features = ["derive"] } log = "0.4.17" -env_logger = "0.10.0" +env_logger = "0.11.3" walkdir = "2" color-eyre = { version = "0.6.2", default-features = false } chrono = { version = "0.4.23", features = ["serde","clock"]} -serde_json = "1.0" +serde_json = "1.0.117" +regex = "*" [dev-dependencies] tempfile = "3.0.0" @@ -39,4 +40,4 @@ name = "benchmarks" harness = false [profile.dev.package.backtrace] -opt-level = 3 \ No newline at end of file +opt-level = 3 diff --git a/zork++/benches/benchmarks.rs b/zork++/benches/benchmarks.rs index f92c4363..9198c435 100644 --- a/zork++/benches/benchmarks.rs +++ b/zork++/benches/benchmarks.rs @@ -8,12 +8,13 @@ use zork::{ cache::{self, ZorkCache}, cli::input::CliArgs, compiler::build_project, - config_file::ZorkConfigFile, + config_file::{self, ZorkConfigFile}, utils::{self, reader::build_model}, }; pub fn build_project_benchmark(c: &mut Criterion) { - let config: ZorkConfigFile = toml::from_str(utils::constants::CONFIG_FILE_MOCK).unwrap(); + 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 cache = ZorkCache::default(); diff --git a/zork++/src/lib/cache/mod.rs b/zork++/src/lib/cache/mod.rs index 893120e2..e05a184c 100644 --- a/zork++/src/lib/cache/mod.rs +++ b/zork++/src/lib/cache/mod.rs @@ -3,7 +3,9 @@ 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::{ fs, @@ -11,6 +13,7 @@ use std::{ path::{Path, PathBuf}, }; +use crate::project_model::sourceset::SourceFile; use crate::{ cli::{ input::CliArgs, @@ -27,13 +30,13 @@ 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.as_ref(); + let compiler = program_data.compiler.cpp_compiler; let cache_path = &program_data .build .output_dir .join("zork") .join("cache") - .join(compiler); + .join(compiler.as_ref()); let cache_file_path = cache_path.join(constants::ZORK_CACHE_FILENAME); @@ -49,8 +52,11 @@ pub fn load(program_data: &ZorkModel<'_>, cli_args: &CliArgs) -> Result) { + 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() + self.load_msvc_metadata(program_data)? } + 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); } + + Ok(()) } /// Runs the tasks just before end the program and save the cache @@ -204,7 +213,7 @@ impl ZorkCache { .entry(PathBuf::from(named_target)) .and_modify(|e| { if !(*e).eq(&commands_details.main.command) { - *e = commands_details.main.command.clone() + e.clone_from(&commands_details.main.command) } }) .or_insert(commands_details.main.command.clone()); @@ -215,26 +224,100 @@ impl ZorkCache { } /// If Windows is the current OS, and the compiler is MSVC, then we will try - /// to locate the path os the vcvars64.bat scripts that launches the - /// Developers Command Prompt - fn load_msvc_metadata(&mut self) { - if self.compilers_metadata.msvc.dev_commands_prompt.is_none() { - self.compilers_metadata.msvc.dev_commands_prompt = - WalkDir::new(constants::MSVC_BASE_PATH) - .into_iter() - .filter_map(Result::ok) - .find(|file| { - file.file_name() - .to_str() - .map(|filename| filename.eq(constants::MS_DEVS_PROMPT_BAT)) - .unwrap_or(false) - }) - .map(|e| e.path().display().to_string()); + /// 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; + let compiler = program_data.compiler.cpp_compiler; + + if msvc.dev_commands_prompt.is_none() { + msvc.dev_commands_prompt = utils::fs::find_file( + Path::new(constants::MSVC_REGULAR_BASE_PATH), + constants::MS_ENV_VARS_BAT, + ) + .map(|walkdir_entry| { + walkdir_entry.path().to_string_lossy().replace( + constants::MSVC_REGULAR_BASE_PATH, + constants::MSVC_REGULAR_BASE_SCAPED_PATH, + ) + }); + let output = std::process::Command::new(constants::WIN_CMD) + .arg("/c") + .arg(msvc.dev_commands_prompt.as_ref().ok_or_eyre("Zork++ wasn't unable to find the VS env vars")?) + .arg("&&") + .arg("set") + .output() + .with_context(|| "Unable to load MSVC pre-requisites. Please, open an issue with the details on upstream")?; + + msvc.env_vars = Self::load_env_vars_from_cmd_output(&output.stdout)?; + // Cloning the useful ones for quick access at call site + msvc.compiler_version = msvc.env_vars.get("VisualStudioVersion").cloned(); + + let vs_stdlib_path = + Path::new(msvc.env_vars.get("VCToolsInstallDir").unwrap()).join("modules"); + msvc.vs_stdlib_path = Some(SourceFile { + path: vs_stdlib_path.clone(), + file_stem: String::from("std"), + extension: compiler.get_default_module_extension().to_string(), + }); + msvc.vs_c_stdlib_path = Some(SourceFile { + path: vs_stdlib_path, + file_stem: String::from("std.compat"), + extension: compiler.get_default_module_extension().to_string(), + }); + let modular_stdlib_byproducts_path = Path::new(&program_data.build.output_dir) + .join(compiler.as_ref()) + .join("modules") + .join("std") // folder + .join("std"); // filename + + // Saving the paths to the precompiled bmi and obj files of the MSVC std implementation + // that will be used to reference the build of the std as a module + msvc.stdlib_bmi_path = + modular_stdlib_byproducts_path.with_extension(compiler.get_typical_bmi_extension()); + msvc.stdlib_obj_path = + modular_stdlib_byproducts_path.with_extension(compiler.get_obj_file_extension()); + + let c_modular_stdlib_byproducts_path = modular_stdlib_byproducts_path; + let compat = String::from("compat."); + msvc.c_stdlib_bmi_path = c_modular_stdlib_byproducts_path + .with_extension(compat.clone() + compiler.get_typical_bmi_extension()); + msvc.c_stdlib_obj_path = c_modular_stdlib_byproducts_path + .with_extension(compat + compiler.get_obj_file_extension()); + } + + Ok(()) + } + + /// Convenient helper to manipulate and store the environmental variables as result of invoking + /// the Windows `SET` cmd command + fn load_env_vars_from_cmd_output(stdout: &[u8]) -> Result> { + let env_vars_str = std::str::from_utf8(stdout)?; + let filter = Regex::new(r"^[a-zA-Z_]+$").unwrap(); + + let mut env_vars: HashMap = HashMap::new(); + for line in env_vars_str.lines() { + // Parse the key-value pair from each line + let mut parts = line.splitn(2, '='); + let key = parts.next().expect("Failed to get key").trim(); + + if filter.is_match(key) { + let value = parts.next().unwrap_or_default().trim().to_string(); + env_vars.insert(key.to_string(), value); + } } + + Ok(env_vars) } /// Looks for the already precompiled `GCC` or `Clang` system headers, /// to avoid recompiling them on every process + /// NOTE: This feature should be deprecated an therefore, removed from Zork++ when GCC and + /// Clang fully implement the required procedures to build the C++ std library as a module fn track_system_modules<'a>( program_data: &'a ZorkModel<'_>, ) -> impl Iterator + 'a { @@ -284,7 +367,7 @@ impl ZorkCache { ) -> CommandExecutionResult { if module_command_line .execution_result - .eq(&CommandExecutionResult::Unreached) + .eq(&CommandExecutionResult::Unprocessed) { if let Some(prev_entry) = self.is_file_cached(module_command_line.path()) { prev_entry.execution_result @@ -320,13 +403,23 @@ impl ZorkCache { .to_str() .unwrap_or_default() .to_string(), - file: source_command_line.file.clone(), + file: source_command_line.filename.clone(), execution_result: self.normalize_execution_result_status(source_command_line), } })); new_commands } + + /// Method that returns the HashMap that holds the enviromental variables that must be passed + /// 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, + } + } } #[derive(Deserialize, Serialize, Debug, Default, Clone)] @@ -360,13 +453,48 @@ pub struct MainCommandLineDetail { 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 system_modules: Vec, + 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 + #[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, +} + +impl MsvcMetadata { + pub fn is_loaded(&self) -> bool { + self.dev_commands_prompt.is_some() && self.vs_stdlib_path.is_some() + } +} + +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +pub struct ClangMetadata { + pub env_vars: EnvVars, +} + +#[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 f135eb44..562b1899 100644 --- a/zork++/src/lib/cli/input/mod.rs +++ b/zork++/src/lib/cli/input/mod.rs @@ -21,7 +21,7 @@ use crate::project_model; /// // Create Template Project /// let parser = CliArgs::parse_from(["", "new", "example", "--git", "--compiler", "clang"]); -/// assert_eq!(parser.command, Command::New{name: "example".to_owned(), git: true, compiler: CppCompiler::CLANG, template: TemplateValues::BASIC}); +/// assert_eq!(parser.command, Command::New{name: "example".to_owned(), git: true, compiler: CppCompiler::CLANG, template: TemplateValues::PARTITIONS}); /// // Run autogenerated project // let parser = CliArgs::parse_from(["", "-vv", "run"]); @@ -30,7 +30,7 @@ use crate::project_model; #[derive(Parser, Debug, Default)] #[command(name = "Zork++")] #[command(author = "Zero Day Code")] -#[command(version = "0.8.8")] +#[command(version = "0.9.0")] #[command( about = "Zork++ is a build system for modern C++ projects", long_about = "Zork++ is a project of Zero Day Code. Find us: https://github.com/zerodaycode/Zork" @@ -83,7 +83,7 @@ pub enum Command { compiler: CppCompiler, #[arg( long, - default_value = "basic", + default_value = "partitions", help = "What configuration file template to use" )] template: TemplateValues, diff --git a/zork++/src/lib/cli/output/arguments.rs b/zork++/src/lib/cli/output/arguments.rs index 1e47ab0b..cbeaf9e4 100644 --- a/zork++/src/lib/cli/output/arguments.rs +++ b/zork++/src/lib/cli/output/arguments.rs @@ -7,7 +7,7 @@ use std::{borrow::Borrow, ffi::OsStr, path::PathBuf}; use serde::{Deserialize, Serialize}; -/// Type for represent a command line argument +/// 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, @@ -27,14 +27,6 @@ impl<'a> From for Argument<'a> { } } -impl<'a> Deref for Argument<'a> { - type Target = &'a str; - - fn deref(&self) -> &Self::Target { - &self.value - } -} - impl<'a> From<&'a Path> for Argument<'a> { fn from(value: &'a Path) -> Self { Self::from(format!("{}", value.display())) @@ -53,6 +45,14 @@ 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> Borrow for Argument<'a> { fn borrow(&self) -> &str { self.value @@ -96,7 +96,8 @@ impl<'a> Arguments<'a> { /// 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 @@ -134,6 +135,9 @@ impl<'a> IntoIterator for Arguments<'a> { } } +/// Isolated module to storing custom procedures to easy 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 { use std::path::Path; @@ -192,3 +196,64 @@ 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 super::Arguments; + + pub(crate) fn generate_std_cmd_args<'a>( + model: &'a ZorkModel<'_>, + cache: &ZorkCache, + stdlib_mode: StdLibMode, + ) -> SourceCommandLine<'a> { + let mut arguments = Arguments::default(); + let msvc = &cache.compilers_metadata.msvc; + + let (stdlib_sf, stdlib_bmi_path, stdlib_obj_path) = if stdlib_mode.eq(&StdLibMode::Cpp) { + ( + msvc.vs_stdlib_path.as_ref().unwrap(), + &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, + ) + }; + + 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! { + "{}", stdlib_bmi_path.display() + }); + arguments.create_and_push(format! { + "/Fo{}", stdlib_obj_path.display() + }); + + SourceCommandLine::from_translation_unit( + stdlib_sf, + arguments, + false, + CommandExecutionResult::default(), + ) + } +} diff --git a/zork++/src/lib/cli/output/commands.rs b/zork++/src/lib/cli/output/commands.rs index f598d976..d1b5f94a 100644 --- a/zork++/src/lib/cli/output/commands.rs +++ b/zork++/src/lib/cli/output/commands.rs @@ -31,31 +31,35 @@ pub fn run_generated_commands( test_mode: bool, ) -> Result { log::info!("Proceeding to execute the generated commands..."); - let mut total_exec_commands = 0; 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 sources = commands - .interfaces + let translation_units = commands + .pre_tasks .iter_mut() + .chain(commands.interfaces.iter_mut()) .chain(commands.implementations.iter_mut()) .chain(commands.sources.iter_mut()); - for source_file in sources { - if !source_file.processed { - let r = execute_command(compiler, program_data, &source_file.args, cache); - source_file.execution_result = CommandExecutionResult::from(&r); - total_exec_commands += 1; + 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", - source_file.file + translation_unit.filename ); cache::save(program_data, cache, commands, test_mode)?; return Err(err); @@ -80,7 +84,6 @@ pub fn run_generated_commands( } } - log::debug!("A total of: {total_exec_commands} command lines has been executed"); cache::save(program_data, cache, commands, test_mode)?; Ok(CommandExecutionResult::Success) } @@ -131,28 +134,12 @@ fn execute_command( ) ); - if compiler.eq(&CppCompiler::MSVC) { - std::process::Command::new( - cache - .compilers_metadata - .msvc - .dev_commands_prompt - .as_ref() - .expect("Zork++ wasn't able to found a correct installation of MSVC"), - ) - .arg("&&") - .arg(compiler.get_driver(&model.compiler)) + 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(" "))) - } else { - std::process::Command::new(compiler.get_driver(&model.compiler)) - .args(arguments) - .spawn()? - .wait() - .with_context(|| format!("[{compiler}] - Command {:?} failed!", arguments.join(" "))) - } } /// The pieces and details for the generated command line for @@ -160,7 +147,7 @@ fn execute_command( #[derive(Debug)] pub struct SourceCommandLine<'a> { pub directory: PathBuf, - pub file: String, + pub filename: String, pub args: Arguments<'a>, pub processed: bool, pub execution_result: CommandExecutionResult, @@ -169,13 +156,18 @@ pub struct SourceCommandLine<'a> { impl<'a> SourceCommandLine<'a> { pub fn from_translation_unit( tu: impl TranslationUnit, - args: Arguments<'a>, + 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(), - file: tu.file_with_extension(), + filename: tu.file_with_extension(), args, processed, execution_result, @@ -183,7 +175,7 @@ impl<'a> SourceCommandLine<'a> { } pub fn path(&self) -> PathBuf { - self.directory.join(Path::new(&self.file)) + self.directory.join(Path::new(&self.filename)) } } @@ -210,6 +202,7 @@ impl<'a> Default for ExecutableCommandLine<'a> { #[derive(Debug)] pub struct Commands<'a> { pub compiler: CppCompiler, + pub pre_tasks: Vec>, pub system_modules: HashMap>, pub interfaces: Vec>, pub implementations: Vec>, @@ -222,6 +215,7 @@ 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), @@ -272,7 +266,7 @@ pub enum CommandExecutionResult { Error, /// A previous state before executing a command line #[default] - Unreached, + Unprocessed, } impl From> for CommandExecutionResult { diff --git a/zork++/src/lib/compiler/mod.rs b/zork++/src/lib/compiler/mod.rs index 2c0986df..ea499db5 100644 --- a/zork++/src/lib/compiler/mod.rs +++ b/zork++/src/lib/compiler/mod.rs @@ -7,9 +7,10 @@ use color_eyre::Result; use std::path::Path; use crate::bounds::{ExecutableTarget, ExtraArgs, TranslationUnit}; -use crate::cli::output::arguments::{clang_args, Arguments}; +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 crate::{ cache::ZorkCache, @@ -33,35 +34,62 @@ pub fn build_project<'a>( // 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) } + // Build the std library as a module + build_modular_stdlib(model, cache, &mut commands); // TODO: ward it with an if for only call this fn for the // 1st - Build the modules build_modules(model, cache, &mut commands)?; // 2nd - Build the non module sources build_sources(model, cache, &mut commands, tests)?; // 3rd - Build the executable or the tests - build_executable(model, &mut commands, tests)?; + build_executable(model, cache, &mut commands, tests)?; Ok(commands) } +/// 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: &ZorkCache, + commands: &mut Commands<'a>, +) { + let compiler = model.compiler.cpp_compiler; + + // TODO: remaining ones: Clang, GCC + if compiler.eq(&CppCompiler::MSVC) { + if !cache.compilers_metadata.msvc.is_loaded() { + return; + } + + let cpp_stdlib = msvc_args::generate_std_cmd_args(model, cache, StdLibMode::Cpp); + commands.pre_tasks.push(cpp_stdlib); + + let c_cpp_stdlib = msvc_args::generate_std_cmd_args(model, cache, StdLibMode::CCompat); + commands.pre_tasks.push(c_cpp_stdlib); + } +} + /// 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? + // 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, commands, &model.tests) + generate_main_command_line_args(model, cache, commands, &model.tests) } else { - generate_main_command_line_args(model, commands, &model.executable) + generate_main_command_line_args(model, cache, commands, &model.executable) } } @@ -79,7 +107,7 @@ fn build_sources<'a>( }; srcs.iter().for_each(|src| if !flag_source_file_without_changes(&model.compiler.cpp_compiler, cache, &src.file()) { - sources::generate_sources_arguments(model, commands, &model.tests, src); + sources::generate_sources_arguments(model, commands, cache, &model.tests, src); } else { let command_line = SourceCommandLine::from_translation_unit( src, Arguments::default(), true, CommandExecutionResult::Cached @@ -124,7 +152,7 @@ fn build_module_interfaces<'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, module_interface, commands); + 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 @@ -149,14 +177,16 @@ fn build_module_implementations<'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, module_impl, commands); + 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 ); 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); + 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 ))) @@ -167,6 +197,7 @@ fn build_module_implementations<'a>( /// 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<()> { @@ -209,10 +240,6 @@ pub fn generate_main_command_line_args<'a>( CppCompiler::MSVC => { arguments.create_and_push("/EHsc"); arguments.create_and_push("/nologo"); - // If /std:c++20 this, else should be the direct options - // available on C++23 to use directly import std by pre-compiling the standard library - arguments.create_and_push("/experimental:module"); - arguments.create_and_push("/stdIfcDir \"$(VC_IFCPath)\""); arguments.create_and_push("/ifcSearchDir"); arguments.create_and_push( out_dir @@ -232,6 +259,9 @@ pub fn generate_main_command_line_args<'a>( .with_extension(constants::BINARY_EXTENSION) .display() )); + // Add the .obj file of the modular stdlib to the linker command + arguments.create_and_push(&cache.compilers_metadata.msvc.stdlib_obj_path); + arguments.create_and_push(&cache.compilers_metadata.msvc.c_stdlib_obj_path); } CppCompiler::GCC => { arguments.create_and_push("-fmodules-ts"); @@ -263,6 +293,7 @@ pub fn generate_main_command_line_args<'a>( 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::{ @@ -282,6 +313,7 @@ mod sources { pub fn generate_sources_arguments<'a>( model: &'a ZorkModel, commands: &mut Commands<'a>, + cache: &ZorkCache, target: &'a impl ExecutableTarget<'a>, source: &'a SourceFile, ) { @@ -290,7 +322,11 @@ mod sources { let mut arguments = Arguments::default(); arguments.push(model.compiler.language_level_arg()); - arguments.create_and_push("-c"); + 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()); @@ -304,12 +340,15 @@ mod sources { } CppCompiler::MSVC => { arguments.create_and_push("/EHsc"); - arguments.create_and_push(Argument::from("/nologo")); - // If /std:c++20 this, else should be the direct options - // available on C++23 to use directly import std by pre-compiling the standard library - arguments.create_and_push("/experimental:module"); - arguments.create_and_push("/stdIfcDir \"$(VC_IFCPath)\""); - + 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 @@ -348,6 +387,7 @@ mod sources { /// 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>, ) { @@ -391,10 +431,16 @@ mod sources { CppCompiler::MSVC => { arguments.create_and_push("/EHsc"); arguments.create_and_push("/nologo"); - arguments.create_and_push("/experimental:module"); - arguments.create_and_push("/stdIfcDir \"$(VC_IFCPath)\""); arguments.create_and_push("/c"); + arguments.create_and_push("/reference"); + arguments.create_and_push(format! { + "std={}", cache.compilers_metadata.msvc.stdlib_bmi_path.display() + }); + arguments.create_and_push("/reference"); + arguments.create_and_push(format! { + "std.compat={}", cache.compilers_metadata.msvc.c_stdlib_bmi_path.display() + }); let implicit_lookup_mius_path = out_dir .join(compiler.as_ref()) .join("modules") @@ -453,6 +499,7 @@ mod sources { /// 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>, ) { @@ -494,9 +541,15 @@ mod sources { CppCompiler::MSVC => { arguments.create_and_push("/EHsc"); arguments.create_and_push("/nologo"); - arguments.create_and_push("-c"); - arguments.create_and_push("/experimental:module"); - arguments.create_and_push("/stdIfcDir \"$(VC_IFCPath)\""); + 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 @@ -568,7 +621,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 autogenerate of the BMI automatically by the compiler, + /// For MSVC, we are relying in the autogeneration feature of the BMI automatically by the compiler, /// so the output file that we need is an obj file (.obj), and not the /// binary module interface (.ifc) pub(crate) fn generate_prebuilt_miu( @@ -626,6 +679,7 @@ mod helpers { /// generate commands for the non processed elements yet. /// /// This is for `GCC` and `Clang` + /// TODO: With the inclusion of std named modules, want we to support this anymore? pub(crate) fn build_sys_modules<'a>( model: &'a ZorkModel, commands: &mut Commands<'a>, @@ -686,9 +740,11 @@ mod helpers { // 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 + // 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 @@ -700,16 +756,19 @@ mod helpers { /// Marks the given source file as already processed, /// or if it should be reprocessed again due to a previous failure status, - /// to avoid losing time rebuilding that module if the source file + /// to avoid losing time rebuilding it if the translation unit /// hasn't been modified since the last build process iteration. /// - /// True means already processed and previous iteration Success + /// 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, ) -> 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; } diff --git a/zork++/src/lib/config_file/build.rs b/zork++/src/lib/config_file/build.rs index 9225efac..62b9f9c5 100644 --- a/zork++/src/lib/config_file/build.rs +++ b/zork++/src/lib/config_file/build.rs @@ -23,7 +23,7 @@ use serde::*; /// /// assert_eq!(config.output_dir, Some("out")); /// ``` -#[derive(Deserialize, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(deny_unknown_fields)] pub struct BuildAttribute<'a> { #[serde(borrow)] diff --git a/zork++/src/lib/config_file/compiler.rs b/zork++/src/lib/config_file/compiler.rs index e1b8437b..fd1f7f59 100644 --- a/zork++/src/lib/config_file/compiler.rs +++ b/zork++/src/lib/config_file/compiler.rs @@ -1,6 +1,6 @@ //! file for represent the available configuration properties within Zork++ //! for setting up the target compiler -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use crate::project_model; @@ -66,7 +66,7 @@ use crate::project_model; /// /// For a test over a real example, please look at the /// [`zork::config_file::ZorkConfigFile`] doc-test -#[derive(Deserialize, Debug, PartialEq, Default)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[serde(deny_unknown_fields)] pub struct CompilerAttribute<'a> { pub cpp_compiler: CppCompiler, @@ -81,7 +81,7 @@ pub struct CompilerAttribute<'a> { } /// The C++ compilers available within Zork++ -#[derive(Deserialize, Debug, Clone, PartialEq, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] pub enum CppCompiler { #[serde(alias = "CLANG", alias = "Clang", alias = "clang")] #[default] @@ -114,7 +114,7 @@ impl Into for CppCompiler { /// /// Variant *LATEST* is the `MSVC` specific way of set the language /// standard level to the latest features available in Microsoft's compiler -#[derive(Deserialize, Debug, Clone, PartialEq, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] pub enum LanguageLevel { #[serde(alias = "20")] #[default] @@ -146,7 +146,7 @@ impl Into for LanguageLevel { /// The standard library (compiler specific) that the user /// desires to link against -#[derive(Deserialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum StdLib { #[serde(alias = "libstdc++", alias = "gccstdlib", alias = "libstdcpp")] STDLIBCPP, diff --git a/zork++/src/lib/config_file/executable.rs b/zork++/src/lib/config_file/executable.rs index d2d1c03d..4c55924a 100644 --- a/zork++/src/lib/config_file/executable.rs +++ b/zork++/src/lib/config_file/executable.rs @@ -39,7 +39,7 @@ use serde::*; /// /// For a test over a real example, please look at the /// [`zork::config_file::ZorkConfigFile`] doc-test -#[derive(Deserialize, Debug, PartialEq, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] #[serde(deny_unknown_fields)] pub struct ExecutableAttribute<'a> { #[serde(borrow)] diff --git a/zork++/src/lib/config_file/mod.rs b/zork++/src/lib/config_file/mod.rs index f01e08d0..dbc9c994 100644 --- a/zork++/src/lib/config_file/mod.rs +++ b/zork++/src/lib/config_file/mod.rs @@ -9,7 +9,7 @@ pub mod tests; use std::fmt::Debug; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use self::{ build::BuildAttribute, compiler::CompilerAttribute, executable::ExecutableAttribute, @@ -42,7 +42,7 @@ use self::{ /// The [`ZorkConfigFile`] is the type that holds /// the whole hierarchy of Zork++ config file attributes /// and properties -#[derive(Deserialize, Debug, Default)] +#[derive(Serialize, Deserialize, Debug, Default)] pub struct ZorkConfigFile<'a> { #[serde(borrow)] pub project: ProjectAttribute<'a>, @@ -57,3 +57,7 @@ pub struct ZorkConfigFile<'a> { #[serde(borrow)] pub tests: Option>, } + +pub fn zork_cfg_from_file(cfg: &'_ str) -> Result, toml::de::Error> { + ::deserialize(&mut toml::Deserializer::new(cfg)) +} diff --git a/zork++/src/lib/config_file/modules.rs b/zork++/src/lib/config_file/modules.rs index e5f7d80d..25727d7a 100644 --- a/zork++/src/lib/config_file/modules.rs +++ b/zork++/src/lib/config_file/modules.rs @@ -1,5 +1,5 @@ //!! 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; +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 /// * `base_ifcs_dir`- Base directory to shortcut the path of the implementation files @@ -17,7 +17,7 @@ use serde::Deserialize; /// const CONFIG_FILE_MOCK: &str = r#" /// base_ifcs_dir = "./ifc" /// interfaces = [ -/// { file = 'math.cppm' }, { file = 'some_module.cppm', module_name = 'math' } +/// { file = 'math.cppm' }, { file = 'some_module.cppm', module_name = 'math' } /// ] /// base_impls_dir = './src' /// implementations = [ @@ -40,7 +40,7 @@ use serde::Deserialize; /// assert_eq!(ifc_1.file, "some_module.cppm"); /// assert_eq!(ifc_1.module_name, Some("math")); /// -/// +/// /// assert_eq!(config.base_impls_dir, Some("./src")); /// /// let impls = config.implementations.unwrap(); @@ -59,7 +59,7 @@ use serde::Deserialize; /// assert_eq!(&gcc_sys_headers[3], &"type_traits"); /// assert_eq!(&gcc_sys_headers[4], &"functional"); /// ``` -#[derive(Deserialize, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct ModulesAttribute<'a> { #[serde(borrow)] pub base_ifcs_dir: Option<&'a str>, @@ -132,7 +132,7 @@ pub struct ModulesAttribute<'a> { /// assert_eq!(ifc_3.file, "some_module_part.cppm"); /// assert_eq!(ifc_3.module_name, Some("math_part")); /// ``` -#[derive(Deserialize, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(deny_unknown_fields)] pub struct ModuleInterface<'a> { #[serde(borrow)] @@ -158,7 +158,7 @@ pub struct ModuleInterface<'a> { /// * `is_internal_partition` - Optional field for declare that the module is actually /// a module for hold implementation details, known as module implementation partitions. /// This option only takes effect with MSVC -#[derive(Deserialize, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct ModulePartition<'a> { #[serde(borrow)] pub module: &'a str, @@ -200,7 +200,7 @@ pub struct ModulePartition<'a> { /// assert_eq!(deps[1], "type_traits"); /// assert_eq!(deps[2], "iostream"); /// ``` -#[derive(Deserialize, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, 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 86e96634..edc22c1c 100644 --- a/zork++/src/lib/config_file/project.rs +++ b/zork++/src/lib/config_file/project.rs @@ -1,5 +1,6 @@ //! Metadata about the user's project -use serde::*; + +use serde::{Deserialize, Serialize}; /// [`ProjectAttribute`] - Metadata about the user's project /// * `name` - The C++ project's name @@ -35,12 +36,14 @@ use serde::*; /// /// For a test over a real example, please look at the /// [`zork::config_file::ZorkConfigFile`] doc-test -#[derive(Deserialize, Debug, PartialEq, Default)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[serde(deny_unknown_fields)] pub struct ProjectAttribute<'a> { + #[serde(borrow)] pub name: &'a str, #[serde(borrow)] pub authors: Option>, pub compilation_db: Option, + #[serde(borrow)] pub project_root: Option<&'a str>, } diff --git a/zork++/src/lib/config_file/tests.rs b/zork++/src/lib/config_file/tests.rs index 73f24a83..a41cf26f 100644 --- a/zork++/src/lib/config_file/tests.rs +++ b/zork++/src/lib/config_file/tests.rs @@ -38,7 +38,7 @@ use serde::*; /// /// For a test over a real example, please look at the /// [`zork::config_file::ZorkConfigFile`] doc-test -#[derive(Deserialize, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct TestsAttribute<'a> { #[serde(borrow)] pub test_executable_name: Option<&'a str>, diff --git a/zork++/src/lib/lib.rs b/zork++/src/lib/lib.rs index cf5ceccf..2dd67abd 100644 --- a/zork++/src/lib/lib.rs +++ b/zork++/src/lib/lib.rs @@ -15,7 +15,7 @@ pub mod utils; /// without having to do fancy work about checking the /// data sent to stdout/stderr pub mod worker { - use crate::utils::fs::get_project_root_absolute_path; + use crate::{config_file, utils::fs::get_project_root_absolute_path}; use std::{fs, path::Path}; use crate::{ @@ -25,7 +25,6 @@ pub mod worker { output::commands::{self, autorun_generated_binary, CommandExecutionResult, Commands}, }, compiler::build_project, - config_file::ZorkConfigFile, project_model::{compiler::CppCompiler, ZorkModel}, utils::{ self, @@ -63,15 +62,6 @@ pub mod worker { ); }; - /* let project_root = if cli_args.root.is_none() { - project - .project_root - .map(Path::new) - .unwrap_or(Path::new(".")) - } else { - Path::new(cli_args.root.as_ref().unwrap()) - }; */ - 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:?}"); @@ -89,7 +79,7 @@ pub mod worker { ) })?; - let config: ZorkConfigFile = toml::from_str(raw_file.as_str()) + 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)?; @@ -162,8 +152,10 @@ pub mod worker { } /// Creates the directory for output the elements generated - /// during the build process. Also, it will generate the - /// ['output_build_dir'/zork], which is a subfolder + /// during the build process based on the client specification. + /// + /// Also, it will generate the + /// [''/zork], which is a subfolder /// where Zork dumps the things that needs to work correctly /// under different conditions. /// @@ -187,17 +179,19 @@ pub mod worker { 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()))?; utils::fs::create_directory(&zork_intrinsics_path)?; - // TODO This possibly would be temporary + // TODO: This possibly gonna be temporary if compiler.eq(&CppCompiler::CLANG) && cfg!(target_os = "windows") { utils::fs::create_file( &zork_intrinsics_path, "std.h", utils::template::resources::STD_HEADER.as_bytes(), )?; + utils::fs::create_file( &zork_intrinsics_path, "zork.modulemap", @@ -215,28 +209,33 @@ pub mod worker { use color_eyre::{eyre::Context, Result}; use tempfile::tempdir; - use crate::config_file::ZorkConfigFile; + use crate::config_file::{self, ZorkConfigFile}; use crate::utils::{reader::build_model, template::resources::CONFIG_FILE}; #[test] fn test_creation_directories() -> Result<()> { let temp = tempdir()?; + let temp_path = temp.path(); + let out_path = temp_path.join("out"); let normalized_cfg_file = CONFIG_FILE .replace("", "clang") + .replace("", "LIBCPP") .replace('\\', "/"); - let zcf: ZorkConfigFile = toml::from_str(&normalized_cfg_file)?; + let zcf: ZorkConfigFile = config_file::zork_cfg_from_file(&normalized_cfg_file)?; let cli_args = CliArgs::parse_from(["", "-vv", "run"]); - let model = build_model(&zcf, &cli_args, temp.path()) + let model = build_model(&zcf, &cli_args, temp_path) .with_context(|| "Error building the project model")?; // This should create and out/ directory in the ./zork++ folder at the root of this project super::create_output_directory(&model)?; - assert!(temp.path().join("out").exists()); - assert!(temp.path().join("out/zork").exists()); - assert!(temp.path().join("out/zork/cache").exists()); - assert!(temp.path().join("out/zork/intrinsics").exists()); + assert!(out_path.exists()); + assert!(out_path.join("clang").exists()); + + assert!(out_path.join("zork").exists()); + assert!(out_path.join("zork").join("cache").exists()); + assert!(out_path.join("zork").join("intrinsics").exists()); Ok(()) } diff --git a/zork++/src/lib/project_model/compiler.rs b/zork++/src/lib/project_model/compiler.rs index 7ac62eab..9fee6de6 100644 --- a/zork++/src/lib/project_model/compiler.rs +++ b/zork++/src/lib/project_model/compiler.rs @@ -147,3 +147,9 @@ impl AsRef for StdLib { } } } + +#[derive(Debug, PartialEq, Eq)] +pub enum StdLibMode { + Cpp, //< The C++ STD library implemented for every vendor + CCompat, //< Same, but extending it with the C ISO standard library +} diff --git a/zork++/src/lib/project_model/sourceset.rs b/zork++/src/lib/project_model/sourceset.rs index cf36fd85..bba7313f 100644 --- a/zork++/src/lib/project_model/sourceset.rs +++ b/zork++/src/lib/project_model/sourceset.rs @@ -1,18 +1,32 @@ use core::fmt; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use crate::bounds::TranslationUnit; use color_eyre::{eyre::Context, Result}; +use serde::{Deserialize, Serialize}; use crate::cli::output::arguments::Argument; -#[derive(Debug, PartialEq, Eq)] -pub enum Source { - File(PathBuf), - Glob(GlobPattern), +// 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; } -#[derive(Debug, PartialEq, Eq)] +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 { pub path: PathBuf, pub file_stem: String, @@ -22,7 +36,7 @@ pub struct SourceFile { impl TranslationUnit for SourceFile { fn file(&self) -> PathBuf { let mut tmp = self.path.join(&self.file_stem).into_os_string(); - tmp.push("."); + tmp.push("."); // TODO: use the correct PATH APIs tmp.push(&self.extension); PathBuf::from(tmp) } @@ -71,6 +85,12 @@ impl fmt::Display for SourceFile { } } +#[derive(Debug, PartialEq, Eq)] +pub enum Source { + File(PathBuf), + Glob(GlobPattern), +} + impl Source { #[inline(always)] pub fn paths(&self) -> Result> { diff --git a/zork++/src/lib/utils/constants.rs b/zork++/src/lib/utils/constants.rs index dfe7ba4f..18403c83 100644 --- a/zork++/src/lib/utils/constants.rs +++ b/zork++/src/lib/utils/constants.rs @@ -15,10 +15,13 @@ pub const COMPILATION_DATABASE: &str = "compile_commands.json"; pub const GCC_CACHE_DIR: &str = "gcm.cache"; -pub const MSVC_BASE_PATH: &str = "C:\\Program Files\\Microsoft Visual Studio"; -pub const MS_DEVS_PROMPT_BAT: &str = "vcvars64.bat"; - +pub const WIN_CMD: &str = "C:\\Windows\\system32\\cmd"; +pub const MSVC_REGULAR_BASE_SCAPED_PATH: &str = + "C:\\\"Program Files\"\\\"Microsoft Visual Studio\""; +pub const MSVC_REGULAR_BASE_PATH: &str = "C:\\Program Files\\Microsoft Visual Studio"; +pub const MS_ENV_VARS_BAT: &str = "vcvars64.bat"; pub const CONFIG_FILE_MOCK: &str = r#" + [project] name = "Zork++" authors = ["zerodaycode.gz@gmail.com"] diff --git a/zork++/src/lib/utils/fs.rs b/zork++/src/lib/utils/fs.rs index d023aad9..a7b838f1 100644 --- a/zork++/src/lib/utils/fs.rs +++ b/zork++/src/lib/utils/fs.rs @@ -6,6 +6,7 @@ use std::{ io::{BufReader, Write}, path::{Path, PathBuf}, }; +use walkdir::WalkDir; use super::constants; @@ -23,6 +24,19 @@ pub fn create_file<'a>(path: &Path, filename: &'a str, buff_write: &'a [u8]) -> } } +/// Tries fo find a file from a given root path by its filename +pub fn find_file(search_root: &Path, target_filename: &str) -> Option { + WalkDir::new(search_root) + .into_iter() + .filter_map(Result::ok) + .find(|file| { + file.file_name() + .to_str() + .map(|filename| filename.contains(target_filename)) + .unwrap_or(false) + }) +} + /// 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() { diff --git a/zork++/src/lib/utils/reader.rs b/zork++/src/lib/utils/reader.rs index 8e3fd747..7e89c08e 100644 --- a/zork++/src/lib/utils/reader.rs +++ b/zork++/src/lib/utils/reader.rs @@ -371,6 +371,7 @@ fn get_sourceset_for(srcs: Vec<&str>, project_root: &Path) -> SourceSet { #[cfg(test)] mod test { + use crate::config_file; use crate::utils::fs; use crate::{ project_model::compiler::{CppCompiler, LanguageLevel, StdLib}, @@ -392,7 +393,7 @@ mod test { cpp_standard = '20' "#; - let config: ZorkConfigFile = toml::from_str(CONFIG_FILE_MOCK)?; + 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); @@ -441,7 +442,8 @@ mod test { #[test] fn test_project_model_with_full_config() -> Result<()> { - let config: ZorkConfigFile = toml::from_str(utils::constants::CONFIG_FILE_MOCK)?; + 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); diff --git a/zork++/src/lib/utils/template/mod.rs b/zork++/src/lib/utils/template/mod.rs index 7dae9040..89a7eb0e 100644 --- a/zork++/src/lib/utils/template/mod.rs +++ b/zork++/src/lib/utils/template/mod.rs @@ -13,14 +13,19 @@ use std::process::Command; /// user code in a modern fashion way. /// /// Base template for the project files and folders: -/// - ./ifc/ +/// - ./ifc /// - math. -/// - ./src/ -/// - math. -/// - math2. +/// - ./src +/// - math. .cpp, .cc, ...> +/// - math2. .cpp, .cc, ...> /// - main.cpp -/// - test -/// - dependencies +/// - test/ +/// - dependencies/ +/// +/// Note that this template is just a pnemonic. Any `C++` project can adhere to +/// whatever they feel that suits them better. Even tho, take in consideration +/// that legacy projects (headers + sources) can differ structurally to better reflect +/// what kind of responsabilities a translation unit has in the `C++` modules world. pub fn create_templated_project( base_path: &Path, project_name: &str, @@ -84,15 +89,21 @@ pub fn create_templated_project( utils::fs::create_file(&path_src, "math.cpp", resources::SRC_MOD_FILE.as_bytes())?; utils::fs::create_file(&path_src, "math2.cpp", resources::SRC_MOD_FILE_2.as_bytes())?; - let zork_conf = match template { - TemplateValues::BASIC => resources::CONFIG_FILE_BASIC, - TemplateValues::PARTITIONS => resources::CONFIG_FILE, + let template = match compiler { + CppCompiler::MSVC => match template { + TemplateValues::BASIC => resources::CONFIG_FILE_BASIC_MSVC, + TemplateValues::PARTITIONS => resources::CONFIG_FILE_MSVC, + }, + CppCompiler::CLANG => match template { + TemplateValues::BASIC => resources::CONFIG_FILE_BASIC, + TemplateValues::PARTITIONS => resources::CONFIG_FILE, + }, + CppCompiler::GCC => match template { + TemplateValues::BASIC => resources::CONFIG_FILE_BASIC_GCC, + TemplateValues::PARTITIONS => resources::CONFIG_FILE_GCC, + }, } - .replace("", compiler.as_ref()) - .replace("", project_name) - .replace("", project_name) - .replace("", project_name) - .replace("cppm", compiler.get_default_module_extension()); + .replace("", project_name); utils::fs::create_file( &project_root, @@ -102,7 +113,7 @@ pub fn create_templated_project( compiler.as_ref(), utils::constants::CONFIG_FILE_EXT ), - zork_conf.as_bytes(), + template.as_bytes(), )?; if git { @@ -112,6 +123,52 @@ 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/main.cpp b/zork++/src/lib/utils/template/resources/main.cpp index 479ccd2e..74fb637e 100644 --- a/zork++/src/lib/utils/template/resources/main.cpp +++ b/zork++/src/lib/utils/template/resources/main.cpp @@ -1,4 +1,3 @@ - const char* compiler = #if defined(__clang__) "Clang"; @@ -6,10 +5,10 @@ const char* compiler = #elif (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) "GCC"; import ; -#elif defined +#elif defined(_MSC_VER) "MSVC"; - #pragma warning(disable:5050) - import std.core; + import std; + import std.compat; #endif import math; @@ -23,7 +22,13 @@ int main() { std::cout << "RESULT of 2 * 8 = " << math::multiply(2, 8) << std::endl; std::cout << "RESULT of 2 / 2 = " << math::divide(2, 2) << std::endl << std::endl; - std::cout << "Testing interface module partitions, by just returning a 42: " + std::cout << "Testing interface module partitions, by just returning the number: " << just_a_42() << std::endl; + + #if defined(_MSC_VER) + printf("Import std.compat to get global names like printf()\n"); + #endif + return 0; } + diff --git a/zork++/src/lib/utils/template/resources/main_basic.cpp b/zork++/src/lib/utils/template/resources/main_basic.cpp index 58aaed6e..09030c4a 100644 --- a/zork++/src/lib/utils/template/resources/main_basic.cpp +++ b/zork++/src/lib/utils/template/resources/main_basic.cpp @@ -1,4 +1,3 @@ - const char* compiler = #if defined(__clang__) "Clang"; @@ -8,8 +7,8 @@ const char* compiler = import ; #elif defined(_MSC_VER) "MSVC"; - #pragma warning(disable:5050) - import std.core; + import std; + import std.compat; #endif import math; @@ -22,5 +21,9 @@ int main() { std::cout << "RESULT of 2 * 8 = " << math::multiply(2, 8) << std::endl; std::cout << "RESULT of 2 / 2 = " << math::divide(2, 2) << std::endl << std::endl; + #if defined(_MSC_VER) + printf("Import std.compat to get global names like printf()\n"); + #endif + return 0; } diff --git a/zork++/src/lib/utils/template/resources/mod.rs b/zork++/src/lib/utils/template/resources/mod.rs index 2cf49bb8..6f8367ed 100644 --- a/zork++/src/lib/utils/template/resources/mod.rs +++ b/zork++/src/lib/utils/template/resources/mod.rs @@ -8,7 +8,13 @@ pub const SRC_MOD_FILE: &str = include_str!("math.cpp"); pub const SRC_MOD_FILE_2: &str = include_str!("math2.cpp"); pub const MAIN: &str = include_str!("main.cpp"); pub const MAIN_BASIC: &str = include_str!("main_basic.cpp"); + pub const CONFIG_FILE: &str = include_str!("zork_example.toml"); +pub const CONFIG_FILE_GCC: &str = include_str!("zork_example_gcc.toml"); +pub const CONFIG_FILE_MSVC: &str = include_str!("zork_example_msvc.toml"); pub const CONFIG_FILE_BASIC: &str = include_str!("zork_example_basic.toml"); +pub const CONFIG_FILE_BASIC_MSVC: &str = include_str!("zork_example_basic_msvc.toml"); +pub const CONFIG_FILE_BASIC_GCC: &str = include_str!("zork_example_basic_gcc.toml"); + pub const STD_HEADER: &str = include_str!("std.h"); pub const ZORK_MODULEMAP: &str = include_str!("zork.modulemap"); diff --git a/zork++/src/lib/utils/template/resources/zork_example.toml b/zork++/src/lib/utils/template/resources/zork_example.toml index 63f51fcc..8ffe0ed6 100644 --- a/zork++/src/lib/utils/template/resources/zork_example.toml +++ b/zork++/src/lib/utils/template/resources/zork_example.toml @@ -2,10 +2,10 @@ [project] name = "" authors = [ "Zero Day Code" ] # Replace this for the real authors -compilation_db = false +compilation_db = true [compiler] -cpp_compiler = "" +cpp_compiler = "clang" cpp_standard = "2b" std_lib = "LIBCPP" @@ -13,28 +13,23 @@ std_lib = "LIBCPP" output_dir = "out" [executable] -executable_name = "" -sources = [ - "/*.cpp" -] +executable_name = "" +sources = [ "*.cpp" ] [tests] tests_executable_name = "zork_proj_tests" -sources = [ - "/*.cpp" -] +sources = [ "*.cpp" ] [modules] -base_ifcs_dir = "/ifc" +base_ifcs_dir = "ifc" interfaces = [ { file = 'math.cppm' }, { 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' } ] -base_impls_dir = "/src" +base_impls_dir = "src" implementations = [ { file = 'math.cpp', dependencies = ['math'] }, { file = 'math2.cpp', dependencies = ['math'] }, ] -sys_modules = ['iostream'] 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 b8a6d9a9..047d1b8e 100644 --- a/zork++/src/lib/utils/template/resources/zork_example_basic.toml +++ b/zork++/src/lib/utils/template/resources/zork_example_basic.toml @@ -2,10 +2,10 @@ [project] name = "" authors = [ "Zero Day Code" ] # Replace this for the real authors -compilation_db = false +compilation_db = true [compiler] -cpp_compiler = "" +cpp_compiler = "clang" cpp_standard = "2b" std_lib = "LIBCPP" @@ -13,25 +13,20 @@ std_lib = "LIBCPP" output_dir = "out" [executable] -executable_name = "" -sources = [ - "/*.cpp" -] +executable_name = "" +sources = [ "*.cpp" ] [tests] tests_executable_name = "zork_proj_tests" -sources = [ - "/*.cpp" -] +sources = [ "*.cpp" ] [modules] -base_ifcs_dir = "/ifc" +base_ifcs_dir = "ifc" interfaces = [ { file = 'math.cppm'} ] -base_impls_dir = "/src" +base_impls_dir = "src" implementations = [ { file = 'math.cpp', dependencies = ['math'] }, { file = 'math2.cpp', dependencies = ['math'] } ] -sys_modules = ['iostream'] 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 new file mode 100644 index 00000000..cbed9090 --- /dev/null +++ b/zork++/src/lib/utils/template/resources/zork_example_basic_gcc.toml @@ -0,0 +1,32 @@ +#This file it's autogenerated as an example of a Zork config file +[project] +name = "" +authors = [ "Zero Day Code" ] # Replace this for the real authors +compilation_db = true + +[compiler] +cpp_compiler = "gcc" +cpp_standard = "23" + +[build] +output_dir = "out" + +[executable] +executable_name = "" +sources = [ "*.cpp" ] + +[tests] +tests_executable_name = "zork_proj_tests" +sources = [ "*.cpp" ] + +[modules] +base_ifcs_dir = "ifc" +interfaces = [ + { file = 'math.cc'} +] +base_impls_dir = "src" +implementations = [ + { file = 'math.cpp', dependencies = ['math'] }, + { file = 'math2.cpp', dependencies = ['math'] } +] +sys_modules = ['iostream'] 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 new file mode 100644 index 00000000..ab3033d4 --- /dev/null +++ b/zork++/src/lib/utils/template/resources/zork_example_basic_msvc.toml @@ -0,0 +1,31 @@ +#This file it's autogenerated as an example of a Zork config file +[project] +name = "" +authors = [ "Zero Day Code" ] # Replace this for the real authors +compilation_db = true + +[compiler] +cpp_compiler = "msvc" +cpp_standard = "latest" + +[build] +output_dir = "out" + +[executable] +executable_name = "" +sources = [ "*.cpp" ] + +[tests] +tests_executable_name = "zork_proj_tests" +sources = [ "*.cpp" ] + +[modules] +base_ifcs_dir = "ifc" +interfaces = [ + { file = 'math.ixx'} +] +base_impls_dir = "src" +implementations = [ + { file = 'math.cpp', dependencies = ['math'] }, + { file = 'math2.cpp', dependencies = ['math'] } +] diff --git a/zork++/src/lib/utils/template/resources/zork_example_gcc.toml b/zork++/src/lib/utils/template/resources/zork_example_gcc.toml new file mode 100644 index 00000000..4671202f --- /dev/null +++ b/zork++/src/lib/utils/template/resources/zork_example_gcc.toml @@ -0,0 +1,35 @@ +#This file it's autogenerated as an example of a Zork config file +[project] +name = "" +authors = [ "Zero Day Code" ] # Replace this for the real authors +compilation_db = true + +[compiler] +cpp_compiler = "gcc" +cpp_standard = "23" + +[build] +output_dir = "out" + +[executable] +executable_name = "" +sources = [ "*.cpp" ] + +[tests] +tests_executable_name = "zork_proj_tests" +sources = [ "*.cpp" ] + +[modules] +base_ifcs_dir = "ifc" +interfaces = [ + { file = 'math.cc' }, + { file = 'interface_partition.cc', partition = { module = 'partitions' } }, + { file = 'internal_partition.cpp', partition = { module = 'partitions', partition_name = 'internal_partition', is_internal_partition = true } }, + { file = 'partitions.cc' } +] +base_impls_dir = "src" +implementations = [ + { file = 'math.cpp', dependencies = ['math'] }, + { file = 'math2.cpp', dependencies = ['math'] }, +] +sys_modules = ['iostream'] diff --git a/zork++/src/lib/utils/template/resources/zork_example_msvc.toml b/zork++/src/lib/utils/template/resources/zork_example_msvc.toml new file mode 100644 index 00000000..54dbfd49 --- /dev/null +++ b/zork++/src/lib/utils/template/resources/zork_example_msvc.toml @@ -0,0 +1,34 @@ +#This file it's autogenerated as an example of a Zork config file +[project] +name = "" +authors = [ "Zero Day Code" ] # Replace this for the real authors +compilation_db = true + +[compiler] +cpp_compiler = "msvc" +cpp_standard = "latest" + +[build] +output_dir = "out" + +[executable] +executable_name = "" +sources = [ "*.cpp" ] + +[tests] +tests_executable_name = "zork_proj_tests" +sources = [ "*.cpp" ] + +[modules] +base_ifcs_dir = "ifc" +interfaces = [ + { file = 'math.ixx' }, + { file = 'interface_partition.ixx', partition = { module = 'partitions' } }, + { file = 'internal_partition.cpp', partition = { module = 'partitions', partition_name = 'internal_partition', is_internal_partition = true } }, + { file = 'partitions.ixx' } +] +base_impls_dir = "src" +implementations = [ + { file = 'math.cpp', dependencies = ['math'] }, + { file = 'math2.cpp', dependencies = ['math'] }, +] diff --git a/zork++/test/test.rs b/zork++/test/test.rs index a72b1b5a..262de897 100644 --- a/zork++/test/test.rs +++ b/zork++/test/test.rs @@ -1,30 +1,40 @@ use clap::Parser; use color_eyre::Result; -use std::fs; use tempfile::tempdir; use zork::cli::input::CliArgs; #[test] fn test_clang_full_process() -> Result<()> { + let project_name = "clang_example"; + let tempdir = tempdir()?; - let path = tempdir.path().to_str().unwrap(); - env_logger::init(); + let path = tempdir.path(); + let binding = path.join(project_name); + let project_root = binding.to_string_lossy(); - assert!(zork::worker::run_zork(&CliArgs::parse_from([ + let new_project_cretion_result = zork::worker::run_zork(&CliArgs::parse_from([ "", "--root", - path, // TODO: pass this path directly to the generated zork++ cfg template file + path.to_str().unwrap(), "new", - "clang_example", + project_name, "--compiler", "clang", "--template", "basic", - ]),) - .is_ok()); + ])); + + assert!( + new_project_cretion_result.is_ok(), + "{}", + new_project_cretion_result.unwrap_err() + ); let process_result = zork::worker::run_zork(&CliArgs::parse_from([ - "", "-vv", "--root", path, + "", + "-vv", + "--root", + &project_root, /* "--driver-path", "clang++-16", // Local cfg issues */ "run", @@ -34,41 +44,106 @@ fn test_clang_full_process() -> Result<()> { Ok(tempdir.close()?) } +#[cfg(target_os = "windows")] +#[test] +fn test_msvc_process_basic_template() -> Result<()> { + let project_name = "msvc_example_basic"; + + let tempdir = tempdir()?; + let path = tempdir.path(); + let binding = path.join(project_name); + let project_root = binding.to_string_lossy(); + + assert!(zork::worker::run_zork(&CliArgs::parse_from([ + "", + "--root", + path.to_str().unwrap(), + "new", + project_name, + "--compiler", + "msvc", + "--template", + "basic" + ])) + .is_ok()); + + assert!(zork::worker::run_zork(&CliArgs::parse_from([ + "", + "-vv", + "--root", + &project_root, + "run" + ])) + .is_ok()); + + Ok(tempdir.close()?) +} + #[cfg(target_os = "windows")] #[test] fn test_msvc_full_process() -> Result<()> { - let temp = tempdir()?; + let project_name = "msvc_example"; + + let tempdir = tempdir()?; + let path = tempdir.path(); + let binding = path.join(project_name); + let project_root = binding.to_string_lossy(); - assert!(zork::worker::run_zork( - &CliArgs::parse_from(["", "new", "msvc_example", "--compiler", "msvc"]), - Path::new(temp.path()) - ) + assert!(zork::worker::run_zork(&CliArgs::parse_from([ + "", + "--root", + path.to_str().unwrap(), + "new", + project_name, + "--compiler", + "msvc" + ])) .is_ok()); - assert!(zork::worker::run_zork( - &CliArgs::parse_from(["", "-vv", "run"]), - Path::new(temp.path()) - ) + assert!(zork::worker::run_zork(&CliArgs::parse_from([ + "", + "-vv", + "--root", + &project_root, + "run" + ])) .is_ok()); - Ok(temp.close()?) + Ok(tempdir.close()?) } #[cfg(target_os = "windows")] #[test] +#[ignore] fn test_gcc_windows_full_process() -> Result<()> { - assert!(zork::worker::run_zork( - &CliArgs::parse - Path::new(".") // Unable to run GCC tests because the gcm.cache folder, that - // we just wasn't able to discover how to specify a directory for it - ) + let project_name = "gcc_example"; + + let tempdir = tempdir()?; + let path = tempdir.path(); + let binding = path.join(project_name); + let project_root = binding.to_string_lossy(); + + assert!(zork::worker::run_zork(&CliArgs::parse_from([ + "", + "--root", + path.to_str().unwrap(), + "new", + project_name, + "--compiler", + "gcc" + ])) .is_ok()); - assert!( - zork::worker::run_zork(&CliArgs::parse_from(["", "-vv", "run"]), Path::new(".")).is_ok() - ); + assert!(zork::worker::run_zork(&CliArgs::parse_from([ + "", + "-vv", + "--root", + &project_root, + "run" + ])) + .is_ok()); - Ok(()) + Ok(tempdir.close()?) } #[cfg(target_os = "linux")] @@ -87,24 +162,42 @@ In module imported at /tmp/.tmpGaFLnR/gcc_example/main.cpp:8:5: compilation terminated. */ fn test_gcc_full_process() -> Result<()> { + use std::fs; + + let project_name = "gcc_example"; + let tempdir = tempdir()?; - let path = tempdir.path().to_str().unwrap(); + let path = tempdir.path(); + let binding = path.join(project_name); + let project_root = binding.to_string_lossy(); - assert!(zork::worker::run_zork(&CliArgs::parse_from([ + let new_project_cretion_result = zork::worker::run_zork(&CliArgs::parse_from([ "", "--root", - path, + path.to_str().unwrap(), "new", - "gcc_example", + project_name, "--compiler", "gcc", - ]),) - .is_ok()); + "--template", + "basic", + ])); assert!( - zork::worker::run_zork(&CliArgs::parse_from(["", "-vv", "--root", path, "run"]),).is_ok() + new_project_cretion_result.is_ok(), + "{}", + new_project_cretion_result.unwrap_err() ); + let process_result = zork::worker::run_zork(&CliArgs::parse_from([ + "", + "-vv", + "--root", + &project_root, + "run", + ])); + assert!(process_result.is_ok(), "{}", process_result.unwrap_err()); + // Clearing the GCC modules cache (weird, isn't generated at the invoked project's root) // maybe we should change dir? but that collide with the purpose of specifiying the project // root clearly @@ -140,6 +233,7 @@ mod local_env_tests { let process = zork::worker::run_zork(&CliArgs::parse_from([ "", "-vv", + "-c", "--root", &path.display().to_string(), "--driver-path",