diff --git a/.github/workflows/linux_gcc_cmake_build.yml b/.github/workflows/linux_gcc_cmake_build.yml index 1a6db7aa..2f3de693 100644 --- a/.github/workflows/linux_gcc_cmake_build.yml +++ b/.github/workflows/linux_gcc_cmake_build.yml @@ -9,10 +9,16 @@ on: env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release - BUILD_FOLDER: _build_gcc_dev_all + BUILD_DEV_ALL_PRESET: gcc_dev_all + BUILD_FOLDER_DEV_ALL: _build_gcc_dev_all + BUILD_DEV_SINGLE_PRESET: gcc_dev_single + BUILD_FOLDER_DEV_SINGLE: _build_gcc_dev_single + BUILD_DEV_INTERFACE_PRESET: gcc_dev_interface + BUILD_FOLDER_DEV_INTERFACE: _build_gcc_dev_interface jobs: - build: + build_all: + name: GCC single and interface Lib # The CMake configure and build commands are platform agnostic and should work equally # well on Windows or Mac. You can convert this to a matrix build if you need # cross-platform coverage. @@ -20,13 +26,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - name: System Packages run: | - sudo apt-get install ninja-build doxygen graphviz gcovr lcov cppcheck clang-tidy + sudo apt-get install ninja-build doxygen graphviz cppcheck clang-tidy - name: Check environment run: | @@ -35,59 +41,296 @@ jobs: clang --version ninja --version doxygen --version - gcovr --version + cppcheck --version + clang-tidy --version + + - name: Configure CMake + run: | + cmake --list-presets + cmake --preset=${{env.BUILD_DEV_ALL_PRESET}} + + - name: Static Analysis + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: cmake --build . --target cppcheck_static_analysis + + - name: Build Release + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + cmake --build . --parallel 2 --config Release + + - name: Bootstrap through CMake + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + cmake --build . --target run_buildcc_lib_bootstrap_linux_gcc --config Release + + - name: BuildExe Layout setup + working-directory: ${{github.workspace}}/.. + run: | + mkdir buildcc_home + export BUILDCC_HOME="${{github.workspace}}/../buildcc_home" + echo $BUILDCC_HOME + cd buildcc_home + mkdir buildcc + mkdir libs + mkdir extensions + cd .. + ls + + - name: BuildExe IM example tiny-process-library + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + export BUILDCC_HOME="${{github.workspace}}/../buildcc_home" + echo $BUILDCC_HOME + cmake --build . --target run_buildexe_im_tpl_linux_gcc --config Release + + - name: CPack Release + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + cpack -C Release -G ZIP + + - name: Upload CPack + uses: actions/upload-artifact@v3 + with: + name: "BuildExe_Linux" + path: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}}/BuildCC-0.1.1-Linux.zip + + - name: Install + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + sudo cmake --install . --config Release + + - name: AfterInstall Example + working-directory: ${{github.workspace}}/example/gcc/AfterInstall + run: | + cmake -B build -G "Ninja Multi-Config" + cmake --build build --parallel 2 --config Release + ./build/Release/build + + - name: Hybrid Single Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_single_example --config Release + + - name: Hybrid Simple Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_simple_example_linux --config Release + + - name: Hybrid Foolib Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_foolib_example_linux --config Release + + - name: Hybrid External Lib Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_externallib_example_linux --config Release + + - name: Hybrid Custom Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_customtarget_example_linux --config Release + + - name: Hybrid Generic Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_generic_example --config Release + + - name: Hybrid PCH Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_pch_example_linux --config Release + + - name: Hybrid Dep Chaining Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_depchaining_example_linux --config Release + + - name: Hybrid Target Info Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_targetinfo_example_linux --config Release + + build_single: + name: GCC single lib + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: System Packages + run: | + sudo apt-get install ninja-build doxygen graphviz cppcheck clang-tidy + + - name: Install LCOV + run: | + git clone -b v1.15 https://github.com/linux-test-project/lcov.git + ls + cd lcov + sudo make install lcov --version + + - name: Check environment + run: | + cmake --version + gcc --version + clang --version + ninja --version + doxygen --version cppcheck --version clang-tidy --version - name: Configure CMake run: | cmake --list-presets - cmake --preset=gcc_dev_all + cmake --preset=${{env.BUILD_DEV_SINGLE_PRESET}} - name: Static Analysis - working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER}} + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} run: cmake --build . --target cppcheck_static_analysis - - name: Build + - name: Build Debug and test # Linux has 2 cores run: | cmake --build --list-presets - cmake --build --preset=gcc_dev_all --parallel 2 + cmake --build --preset=${{env.BUILD_DEV_SINGLE_PRESET}} --parallel 2 --config Debug + ctest --preset=${{env.BUILD_DEV_SINGLE_PRESET}} --parallel 2 -C Debug - - name: Test + - name: Codecov + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} + run: | + cmake --build . --target lcov_coverage + cat ../codecov.yml | curl --data-binary @- https://codecov.io/validate + bash <(curl -s https://codecov.io/bash) -f coverage_truncated.info || echo "Codecov did not collect coverage reports" + + - name: Build Release for example + run: | + cmake --build --preset=${{env.BUILD_DEV_SINGLE_PRESET}} --parallel 2 --config Release + + - name: Bootstrap through CMake + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} + run: | + cmake --build . --target run_buildcc_lib_bootstrap_linux_gcc --config Release + + - name: BuildExe Layout setup + working-directory: ${{github.workspace}}/.. + run: | + mkdir buildcc_home + export BUILDCC_HOME="${{github.workspace}}/../buildcc_home" + echo $BUILDCC_HOME + cd buildcc_home + mkdir buildcc + mkdir libs + mkdir extensions + cd .. + ls + + - name: BuildExe IM example tiny-process-library + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} run: | - ctest --preset=gcc_dev_all --parallel 2 + export BUILDCC_HOME="${{github.workspace}}/../buildcc_home" + echo $BUILDCC_HOME + cmake --build . --target run_buildexe_im_tpl_linux_gcc --config Release + + # - name: TODO, BuildExe SM simple hyrid example - name: Install - working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER}} + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} run: | - sudo cmake --install . + sudo cmake --install . --config Release + + - name: AfterInstall Example + working-directory: ${{github.workspace}}/example/gcc/AfterInstall + run: | + cmake -B build -G "Ninja Multi-Config" + cmake --build build --parallel 2 --config Release + ./build/Release/build + + - name: Hybrid Single Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} + run: | + cmake --build . --target run_hybrid_single_example --config Release - name: Hybrid Simple Example - working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER}} + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} run: | - cmake --build . --target run_hybrid_simple_example_linux + cmake --build . --target run_hybrid_simple_example_linux --config Release - name: Hybrid Foolib Example - working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER}} + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} run: | - cmake --build . --target run_hybrid_foolib_example_linux + cmake --build . --target run_hybrid_foolib_example_linux --config Release - name: Hybrid External Lib Example - working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER}} + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} run: | - cmake --build . --target run_hybrid_externallib_example_linux + cmake --build . --target run_hybrid_externallib_example_linux --config Release - name: Hybrid Custom Target Example - working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER}} + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} run: | - cmake --build . --target run_hybrid_customtarget_example_linux + cmake --build . --target run_hybrid_customtarget_example_linux --config Release - - name: AfterInstall Example - working-directory: ${{github.workspace}}/example/gcc/AfterInstall + - name: Hybrid Generic Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} + run: | + cmake --build . --target run_hybrid_generic_example --config Release + + - name: Hybrid PCH Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} + run: | + cmake --build . --target run_hybrid_pch_example_linux --config Release + + - name: Hybrid Dep Chaining Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} + run: | + cmake --build . --target run_hybrid_depchaining_example_linux --config Release + + - name: Hybrid Target Info Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_SINGLE}} + run: | + cmake --build . --target run_hybrid_targetinfo_example_linux --config Release + + build_interface: + name: GCC interface lib + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: System Packages + run: | + sudo apt-get install ninja-build doxygen graphviz cppcheck clang-tidy + + - name: Check environment + run: | + cmake --version + gcc --version + clang --version + ninja --version + doxygen --version + cppcheck --version + clang-tidy --version + + - name: Configure CMake + run: | + cmake --list-presets + cmake --preset=${{env.BUILD_DEV_INTERFACE_PRESET}} + + - name: Static Analysis + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_DEV_INTERFACE}} + run: cmake --build . --target cppcheck_static_analysis + + - name: Build Debug + # Linux has 2 cores + run: | + cmake --build --list-presets + cmake --build --preset=${{env.BUILD_DEV_INTERFACE_PRESET}} --parallel 2 --config Debug + + - name: Test run: | - cmake -B build -G Ninja - cmake --build build --parallel 2 - cd build - ./build + ctest --preset=${{env.BUILD_DEV_INTERFACE_PRESET}} --parallel 2 -C Debug diff --git a/.github/workflows/msvc-analysis.yml b/.github/workflows/msvc-analysis.yml new file mode 100644 index 00000000..5590b87c --- /dev/null +++ b/.github/workflows/msvc-analysis.yml @@ -0,0 +1,67 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# +# Find more information at: +# https://github.com/microsoft/msvc-code-analysis-action + +name: Microsoft C++ Code Analysis + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: "43 11 * * 4" + +env: + # Path to the CMake build directory. + BUILD_TYPE: Release + BUILD_MSVC_PRESET: msvc_analysis + build: "${{ github.workspace }}/build" + +jobs: + analyze: + name: Analyze + runs-on: windows-2019 + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + submodules: true + + - name: Configure CMake + run: | + cmake --list-presets + cmake --preset=${{env.BUILD_MSVC_PRESET}} -B ${{ env.build }} + + # NOTE, We need to build since generated header files are used + - name: Build CMake + run: cmake --build ${{ env.build }} --config ${{ env.BUILD_TYPE }} + + - name: Initialize MSVC Code Analysis + uses: microsoft/msvc-code-analysis-action@main + # Provide a unique ID to access the sarif output path + id: run-analysis + with: + cmakeBuildDirectory: ${{ env.build }} + buildConfiguration: ${{ env.BUILD_TYPE }} + # Ruleset file that will determine what checks will be run + ruleset: NativeRecommendedRules.ruleset + ignoredPaths: third_party;build/buildcc/schema/generated + + # Upload SARIF file to GitHub Code Scanning Alerts + - name: Upload SARIF to GitHub + uses: github/codeql-action/upload-sarif@v1 + with: + sarif_file: ${{ steps.run-analysis.outputs.sarif }} + + # Upload SARIF file as an Artifact to download and view + - name: Upload SARIF as an Artifact + uses: actions/upload-artifact@v3 + with: + name: sarif-file + path: ${{ steps.run-analysis.outputs.sarif }} diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 00000000..59c8c35a --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,43 @@ +name: github pages + +on: + push: + branches: + - main # Set a branch name to trigger deployment + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + deploy: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Setup + run: | + sudo apt-get update + sudo apt-get install ninja-build doxygen graphviz python3-sphinx + pip install breathe sphinx_rtd_theme furo sphinxcontrib-plantuml + + - name: Configure CMake + shell: bash + working-directory: ${{github.workspace}} + run: | + cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILDCC_DOCUMENTATION=ON + + - name: Doxygen + Sphinx + working-directory: ${{github.workspace}}/build + shell: bash + run: | + cmake --build . --target doxygen_documentation + cmake --build . --target sphinx_documentation + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/output diff --git a/.github/workflows/win_cmake_build.yml b/.github/workflows/win_cmake_build.yml new file mode 100644 index 00000000..3844fb97 --- /dev/null +++ b/.github/workflows/win_cmake_build.yml @@ -0,0 +1,249 @@ +name: Windows CMake build + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + BUILD_MSVC_PRESET: msvc_dev_all + BUILD_FOLDER_MSVC_DEV_ALL: _build_msvc_dev_all + BUILD_CLANG_PRESET: clang_dev_all + BUILD_FOLDER_CLANG_DEV_ALL: _build_clang_dev_all + +jobs: + build_msvc: + name: MSVC single and interface Lib + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - uses: ilammy/msvc-dev-cmd@v1 + + - name: Check environment + run: | + cmake --version + cl ? + clang --version + clang-tidy --version + python --version + pip --version + + - name: Configure CMake + run: | + cmake --list-presets + cmake --preset=${{env.BUILD_MSVC_PRESET}} + + - name: Build Debug and test + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + cmake --build . --parallel 2 --config Debug + ctest . --parallel 2 -C Debug + + - name: Build Release + # Linux has 2 cores + run: | + cmake --build --list-presets + cmake --build --preset=${{env.BUILD_MSVC_PRESET}} --config Release --parallel 2 + + - name: Bootstrap through CMake + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + cmake --build . --config Release --target run_buildcc_lib_bootstrap_win_msvc --parallel 2 + + - name: BuildExe Layout setup + working-directory: ${{github.workspace}}/.. + run: | + mkdir buildcc_home + $env:BUILDCC_HOME = "${{github.workspace}}/../buildcc_home" + $env:BUILDCC_HOME + cd buildcc_home + mkdir buildcc + mkdir libs + mkdir extensions + cd .. + dir + + - name: BuildExe IM example tiny-process-library + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + $env:BUILDCC_HOME = "${{github.workspace}}/../buildcc_home" + cmake --build . --config Release --target run_buildexe_im_tpl_win_msvc + + # - name: TODO, BuildExe SM simple hyrid example + + - name: CPack Release + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + cpack -C Release -G ZIP + + - name: Upload CPack + uses: actions/upload-artifact@v3 + with: + name: "BuildExe_Win" + path: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}}/BuildCC-0.1.1-win64.zip + + - name: Install + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + cmake --install . --config Release + + - name: AfterInstall Example + working-directory: ${{github.workspace}}/example/gcc/AfterInstall + run: | + $env:Path += ";C:/Program Files (x86)/BuildCC" + cmake -B build + cmake --build build --config Release --parallel 2 + cd build/Release + ls + .\build.exe + + - name: Hybrid Single Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + cmake --build . --config Release --parallel 2 --target run_hybrid_single_example + + - name: Hybrid Simple Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + cmake --build . --config Release --parallel 2 --target run_hybrid_simple_example_win + + - name: Hybrid Foolib Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + cmake --build . --config Release --parallel 2 --target run_hybrid_foolib_example_win + + - name: Hybrid External Lib Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + cmake --build . --config Release --parallel 2 --target run_hybrid_externallib_example_win + + - name: Hybrid Custom Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + cmake --build . --config Release --parallel 2 --target run_hybrid_customtarget_example_win + + - name: Hybrid Generic Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + cmake --build . --config Release --parallel 2 --target run_hybrid_generic_example + + - name: Hybrid PCH Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + cmake --build . --config Release --parallel 2 --target run_hybrid_pch_example_win + + - name: Hybrid Dep Chaining Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + cmake --build . --config Release --parallel 2 --target run_hybrid_depchaining_example_win + + - name: Hybrid Target Info Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_MSVC_DEV_ALL}} + run: | + cmake --build . --config Release --parallel 2 --target run_hybrid_targetinfo_example_win + + build_clang: + name: Clang single and interface Lib + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - uses: ilammy/msvc-dev-cmd@v1 + + - name: Check environment + run: | + cmake --version + cl ? + clang --version + clang-tidy --version + python --version + pip --version + + - name: Configure CMake + run: | + cmake --list-presets + cmake --preset=${{env.BUILD_CLANG_PRESET}} + + - name: Build + # Linux has 2 cores + run: | + cmake --build --list-presets + cmake --build --preset=${{env.BUILD_CLANG_PRESET}} --parallel 2 --config Release + + - name: Install + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_CLANG_DEV_ALL}} + run: | + cmake --install . --config Release + + - name: AfterInstall Example + working-directory: ${{github.workspace}}/example/gcc/AfterInstall + run: | + $env:Path += ";C:/Program Files (x86)/BuildCC" + cmake -B build + cmake --build build --parallel 2 --config Release + cd build/Release + ls + .\build.exe + + - name: Hybrid Single Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_CLANG_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_single_example --config Release + + - name: Hybrid Simple Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_CLANG_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_simple_example_win --config Release + + - name: Hybrid Foolib Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_CLANG_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_foolib_example_win --config Release + + - name: Hybrid External Lib Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_CLANG_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_externallib_example_win --config Release + + - name: Hybrid Custom Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_CLANG_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_customtarget_example_win --config Release + + - name: Hybrid Generic Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_CLANG_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_generic_example --config Release + + - name: Hybrid PCH Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_CLANG_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_pch_example_win --config Release + + - name: Hybrid Dep Chaining Target Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_CLANG_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_depchaining_example_win --config Release + + - name: Hybrid Target Info Example + working-directory: ${{github.workspace}}/${{env.BUILD_FOLDER_CLANG_DEV_ALL}} + run: | + cmake --build . --target run_hybrid_targetinfo_example_win --config Release diff --git a/.gitignore b/.gitignore index dc987071..3394fcf1 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,10 @@ _deps *build/ *_build_*/ .vscode/ +.vs/ +.idea/ +.cache/ +docs/output # Files *.gcov diff --git a/.gitmodules b/.gitmodules index e8ed3fe1..97699a1c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,21 +1,24 @@ -[submodule "flatbuffers"] - path = flatbuffers - url = https://github.com/google/flatbuffers.git +[submodule "json"] + path = third_party/json + url = https://github.com/nlohmann/json.git [submodule "spdlog"] - path = spdlog + path = third_party/spdlog url = https://github.com/gabime/spdlog.git [submodule "cpputest"] - path = cpputest + path = third_party/cpputest url = https://github.com/cpputest/cpputest.git [submodule "fmt"] - path = fmt + path = third_party/fmt url = https://github.com/fmtlib/fmt.git [submodule "CLI11"] - path = CLI11 + path = third_party/CLI11 url = https://github.com/CLIUtils/CLI11.git [submodule "taskflow"] - path = taskflow + path = third_party/taskflow url = https://github.com/taskflow/taskflow.git [submodule "tiny-process-library"] - path = tiny-process-library + path = third_party/tiny-process-library url = https://gitlab.com/eidheim/tiny-process-library.git +[submodule "optional"] + path = third_party/tl_optional + url = https://github.com/TartanLlama/optional.git diff --git a/CLI11 b/CLI11 deleted file mode 160000 index 5cb3efab..00000000 --- a/CLI11 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5cb3efabce007c3a0230e4cc2e27da491c646b6c diff --git a/CMakeLists.txt b/CMakeLists.txt index 635ef019..669219e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10.0) +cmake_minimum_required(VERSION 3.16) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -10,16 +10,28 @@ project(BuildCC LANGUAGES CXX ) +# User options +option(BUILDCC_INSTALL "Enable BuildCC Installation" ON) +option(BUILDCC_BUILD_AS_SINGLE_LIB "Build all internal libs and modules as part of the buildcc library" ON) +option(BUILDCC_BUILD_AS_INTERFACE "Build all internal libs and modules seperately and link" OFF) + +option(BUILDCC_BUILDEXE "Standalone BuildCC buildsystem executable" ON) +option(BUILDCC_BOOTSTRAP_THROUGH_CMAKE "Bootstrap buildcc through CMake" OFF) + +# NOTE, Conflict with Clang-Tidy on certain compilers +option(BUILDCC_PRECOMPILE_HEADERS "Enable BuildCC precompile headers" OFF) +option(BUILDCC_EXAMPLES "Enable BuildCC Examples" OFF) + +# Dev options option(BUILDCC_TESTING "Enable BuildCC Testing" OFF) -option(BUILDCC_INSTALL "Enable Buildcc Installation" ON) -option(BUILDCC_EXAMPLES "Enable Buildcc Examples" OFF) +# Dev Tool options option(BUILDCC_CLANGTIDY "Enable ClangTidy" OFF) option(BUILDCC_CPPCHECK "Enable CppCheck" OFF) option(BUILDCC_DOCUMENTATION "Enable Documentation" OFF) +# Compiler options # NOTE, This option is required for clang compilers, architecture x86_64-pc-windows-msvc -# Flatbuffers library uses `std::system` internally which causes a deprecated error option(BUILDCC_NO_DEPRECATED "Disable Deprecated" OFF) if (${BUILDCC_NO_DEPRECATED}) add_compile_options("-Wno-deprecated") @@ -27,7 +39,7 @@ endif() # Testing set(BUILD_TESTING OFF CACHE BOOL "Third Party modules use these options") -if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" AND ${BUILDCC_TESTING}) +if (${BUILDCC_TESTING}) set(TESTING ON) message("Enabling unit-testing") message("Compiler identification: ${CMAKE_CXX_COMPILER_ID}") @@ -44,7 +56,8 @@ endif() # https://gitlab.kitware.com/cmake/cmake/-/issues/20512 # Issue with MSVC CL Compilers -if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" AND ${BUILDCC_CLANGTIDY}) +if (${BUILDCC_CLANGTIDY}) +message("USER CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}") set(CLANGTIDY ON) message("Enabling clang-tidy") else() @@ -56,50 +69,21 @@ include(cmake/tool/cppcheck.cmake) include(cmake/tool/doxygen.cmake) # Libraries - -# TODO, Update FLATBUFFERS option to conditionally compile FLATC - -# Set flatbuffer specific options here -set(FLATBUFFERS_BUILD_CPP17 ON CACHE BOOL "Flatbuffers C++17 support") -set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "Flatbuffers build tests") -set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "Flatbuffers build flathash") -set(FLATBUFFERS_BUILD_FLATLIB ON CACHE BOOL "Flatbuffers build flatlib") -set(FLATBUFFERS_LIBCXX_WITH_CLANG OFF CACHE BOOL "Flatbuffers LIBCXX") -# set(FLATBUFFERS_BUILD_SHAREDLIB ON CACHE BOOL "Flatbuffers built as dynamic library") -add_subdirectory(flatbuffers) - -set(FMT_INSTALL ON CACHE BOOL "Fmt install") -add_subdirectory(fmt) - -# set(SPDLOG_BUILD_SHARED ON CACHE BOOL "Spdlog built as dynamic library") -set(SPDLOG_INSTALL ON CACHE BOOL "Spdlog install") -set(SPDLOG_FMT_EXTERNAL OFF CACHE BOOL "Spdlog FMT external lib") -set(SPDLOG_FMT_EXTERNAL_HO ON CACHE BOOL "Spdlog FMT header only external lib") -add_subdirectory(spdlog) - +include(cmake/target/json.cmake) +include(cmake/target/fmt.cmake) +include(cmake/target/spdlog.cmake) include(cmake/target/cli11.cmake) - -set(TF_BUILD_TESTS OFF CACHE BOOL "TF Tests") -set(TF_BUILD_EXAMPLES OFF CACHE BOOL "TF Examples") -add_subdirectory(taskflow) - -add_subdirectory(tiny-process-library) +include(cmake/target/taskflow.cmake) include(cmake/target/tpl.cmake) +include(cmake/target/tl_optional.cmake) if (${TESTING}) - set(C++11 ON CACHE BOOL "CppUTests C++11 support") - set(CPPUTEST_FLAGS OFF CACHE BOOL "CppUTests Flags off") - set(WERROR ON CACHE BOOL "CppUTests all errors") - set(LONGLONG ON CACHE BOOL "CppUTests Long Long support") - set(TESTS OFF CACHE BOOL "CppUTests tests off") - set(TESTS_BUILD_DISCOVER OFF CACHE BOOL "CppUTests Tests discover") - set(VERBOSE_CONFIG OFF CACHE BOOL "Config print to screen") - add_subdirectory(cpputest) + include(cmake/target/cpputest.cmake) endif() # Coverage -if (${TESTING}) +if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" AND ${TESTING}) include(cmake/coverage/lcov.cmake) include(cmake/coverage/gcovr.cmake) endif() @@ -120,8 +104,21 @@ if (${BUILDCC_INSTALL}) endif() if (${BUILDCC_EXAMPLES}) + add_subdirectory(example/hybrid/single) add_subdirectory(example/hybrid/simple) add_subdirectory(example/hybrid/foolib) add_subdirectory(example/hybrid/external_lib) add_subdirectory(example/hybrid/custom_target) + add_subdirectory(example/hybrid/generic) + add_subdirectory(example/hybrid/pch) + add_subdirectory(example/hybrid/dep_chaining) + add_subdirectory(example/hybrid/target_info) +endif() + +if (${BUILDCC_BUILDEXE}) + add_subdirectory(buildexe) +endif() + +if (${BUILDCC_BOOTSTRAP_THROUGH_CMAKE}) + add_subdirectory(bootstrap) endif() diff --git a/CMakePresets.json b/CMakePresets.json index 33256e2b..6b1d4c4b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -8,40 +8,98 @@ "configurePresets": [ { "name": "gcc_dev_all", - "displayName": "GCC all options", + "displayName": "GCC full build", "description": "GCC build with all developer options", - "generator": "Ninja", + "generator": "Ninja Multi-Config", "binaryDir": "${sourceDir}/_build_gcc_dev_all", "cacheVariables": { - "BUILDCC_TESTING": true, + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", + "BUILDCC_INSTALL": true, + "BUILDCC_BUILD_AS_SINGLE_LIB": true, + "BUILDCC_BUILD_AS_INTERFACE": true, + "BUILDCC_BUILDEXE": true, + "BUILDCC_BOOTSTRAP_THROUGH_CMAKE": true, + "BUILDCC_PRECOMPILE_HEADERS": true, "BUILDCC_EXAMPLES": true, - "BUILDCC_CPPCHECK": true, + "BUILDCC_TESTING": true, "BUILDCC_CLANGTIDY": true, + "BUILDCC_CPPCHECK": true, + "BUILDCC_DOCUMENTATION": false, + "BUILDCC_NO_DEPRECATED": false + } + }, + { + "name": "gcc_dev_single", + "displayName": "GCC single lib build", + "description": "GCC build for single lib buildcc", + "generator": "Ninja Multi-Config", + "binaryDir": "${sourceDir}/_build_gcc_dev_single", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", "BUILDCC_INSTALL": true, - "BUILDCC_NO_DEPRECATED": false, - "BUILDCC_DOCUMENTATION": true, + "BUILDCC_BUILD_AS_SINGLE_LIB": true, + "BUILDCC_BUILD_AS_INTERFACE": false, + "BUILDCC_BUILDEXE": true, + "BUILDCC_BOOTSTRAP_THROUGH_CMAKE": true, + "BUILDCC_PRECOMPILE_HEADERS": true, + "BUILDCC_EXAMPLES": true, + "BUILDCC_TESTING": true, + "BUILDCC_CLANGTIDY": true, + "BUILDCC_CPPCHECK": true, + "BUILDCC_DOCUMENTATION": false, + "BUILDCC_NO_DEPRECATED": false + } + }, + { + "name": "gcc_dev_interface", + "displayName": "GCC interface lib build", + "description": "GCC build for interface lib buildcc", + "generator": "Ninja Multi-Config", + "binaryDir": "${sourceDir}/_build_gcc_dev_interface", + "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "CMAKE_C_COMPILER": "gcc", - "CMAKE_CXX_COMPILER": "g++" + "CMAKE_CXX_COMPILER": "g++", + "BUILDCC_INSTALL": true, + "BUILDCC_BUILD_AS_SINGLE_LIB": false, + "BUILDCC_BUILD_AS_INTERFACE": true, + "BUILDCC_BUILDEXE": false, + "BUILDCC_BOOTSTRAP_THROUGH_CMAKE": false, + "BUILDCC_PRECOMPILE_HEADERS": true, + "BUILDCC_EXAMPLES": false, + "BUILDCC_TESTING": true, + "BUILDCC_CLANGTIDY": true, + "BUILDCC_CPPCHECK": true, + "BUILDCC_DOCUMENTATION": false, + "BUILDCC_NO_DEPRECATED": false } }, { "name": "clang_dev_all", "displayName": "Clang all options", "description": "Clang build with all developer options", - "generator": "Ninja", + "generator": "Ninja Multi-Config", "binaryDir": "${sourceDir}/_build_clang_dev_all", "cacheVariables": { - "BUILDCC_TESTING": false, - "BUILDCC_EXAMPLES": true, - "BUILDCC_CPPCHECK": true, - "BUILDCC_CLANGTIDY": true, - "BUILDCC_INSTALL": true, - "BUILDCC_NO_DEPRECATED": true, - "BUILDCC_DOCUMENTATION": true, "CMAKE_BUILD_TYPE": "Debug", "CMAKE_C_COMPILER": "clang", - "CMAKE_CXX_COMPILER": "clang++" + "CMAKE_CXX_COMPILER": "clang++", + "BUILDCC_INSTALL": true, + "BUILDCC_BUILD_AS_SINGLE_LIB": true, + "BUILDCC_BUILD_AS_INTERFACE": true, + "BUILDCC_BUILDEXE": true, + "BUILDCC_BOOTSTRAP_THROUGH_CMAKE": true, + "BUILDCC_PRECOMPILE_HEADERS": true, + "BUILDCC_EXAMPLES": true, + "BUILDCC_TESTING": false, + "BUILDCC_CLANGTIDY": true, + "BUILDCC_CPPCHECK": false, + "BUILDCC_DOCUMENTATION": false, + "BUILDCC_NO_DEPRECATED": true } }, { @@ -51,14 +109,39 @@ "generator": "Visual Studio 16 2019", "binaryDir": "${sourceDir}/_build_msvc_dev_all", "cacheVariables": { - "BUILDCC_TESTING": false, - "BUILDCC_EXAMPLES": true, - "BUILDCC_CPPCHECK": true, - "BUILDCC_CLANGTIDY": true, "BUILDCC_INSTALL": true, - "BUILDCC_NO_DEPRECATED": false, - "BUILDCC_DOCUMENTATION": true, - "CMAKE_BUILD_TYPE": "Debug" + "BUILDCC_BUILD_AS_SINGLE_LIB": true, + "BUILDCC_BUILD_AS_INTERFACE": true, + "BUILDCC_BUILDEXE": true, + "BUILDCC_BOOTSTRAP_THROUGH_CMAKE": true, + "BUILDCC_PRECOMPILE_HEADERS": true, + "BUILDCC_EXAMPLES": true, + "BUILDCC_TESTING": true, + "BUILDCC_CLANGTIDY": false, + "BUILDCC_CPPCHECK": false, + "BUILDCC_DOCUMENTATION": false, + "BUILDCC_NO_DEPRECATED": false + } + }, + { + "name": "msvc_analysis", + "displayName": "MSVC Analysis", + "description": "MSVC Analysis for CI/CD", + "generator": "Visual Studio 16 2019", + "binaryDir": "${sourceDir}/_build_msvc_analysis", + "cacheVariables": { + "BUILDCC_INSTALL": false, + "BUILDCC_BUILD_AS_SINGLE_LIB": true, + "BUILDCC_BUILD_AS_INTERFACE": false, + "BUILDCC_BUILDEXE": false, + "BUILDCC_BOOTSTRAP_THROUGH_CMAKE": false, + "BUILDCC_PRECOMPILE_HEADERS": false, + "BUILDCC_EXAMPLES": false, + "BUILDCC_TESTING": false, + "BUILDCC_CLANGTIDY": false, + "BUILDCC_CPPCHECK": false, + "BUILDCC_DOCUMENTATION": false, + "BUILDCC_NO_DEPRECATED": false } } ], @@ -67,6 +150,14 @@ "name": "gcc_dev_all", "configurePreset": "gcc_dev_all" }, + { + "name": "gcc_dev_single", + "configurePreset": "gcc_dev_single" + }, + { + "name": "gcc_dev_interface", + "configurePreset": "gcc_dev_interface" + }, { "name": "clang_dev_all", "configurePreset": "clang_dev_all" @@ -87,6 +178,28 @@ "noTestsAction": "error", "stopOnFailure": true } + }, + { + "name": "gcc_dev_single", + "configurePreset": "gcc_dev_single", + "output": { + "outputOnFailure": true + }, + "execution": { + "noTestsAction": "error", + "stopOnFailure": true + } + }, + { + "name": "gcc_dev_interface", + "configurePreset": "gcc_dev_interface", + "output": { + "outputOnFailure": true + }, + "execution": { + "noTestsAction": "error", + "stopOnFailure": true + } } ] } diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 224bf0ab..b19d3a63 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -1,10 +1,22 @@ # Dependencies +These third party libraries are added as submodules since they aren't meant to be modified by this project. + +### Adding a submodule + +`git submodule add [git_url] third_party/[foldername]` + +### Removing a submodule + +- `git rm --cached path_to_submodule` (no trailing slash) +- Delete relevant line from `.gitmodules` file +- Delete relevant section from `.git/config` file + ## Main - fmt (Formatting) - spdlog (Logging) -- flatbuffers (Serialization) +- json (Serialization) - CLI11 (Argument Parsing) - Taskflow (Parallel Programming) @@ -12,6 +24,13 @@ - cpputest (Unit Testing / Mocking) +## Utility + +- tl_optional (Better optional support) + - Synced with branch origin/master (May 2, 2021) + - Commit Id: c28fcf74d207fc667c4ed3dbae4c251ea551c8c1 + - Needed fix: #45 + ## Tools - [x] clangformat (auto) diff --git a/LICENSE b/LICENSE index 7cfc99ac..45d14401 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2021 Niket Naidu + Copyright 2021-2022 Niket Naidu Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index be53f7bb..d3abdfa3 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,25 @@ # Build in CPP _[BuildCC]_ +[![Linux GCC build](https://github.com/coder137/build_in_cpp/actions/workflows/linux_gcc_cmake_build.yml/badge.svg)](https://github.com/coder137/build_in_cpp/actions/workflows/linux_gcc_cmake_build.yml) + +[![Windows CMake build](https://github.com/coder137/build_in_cpp/actions/workflows/win_cmake_build.yml/badge.svg)](https://github.com/coder137/build_in_cpp/actions/workflows/win_cmake_build.yml) + +[![codecov](https://codecov.io/gh/coder137/build_in_cpp/branch/main/graph/badge.svg?token=M71H05PIDW)](https://codecov.io/gh/coder137/build_in_cpp) + Build C, C++ and ASM files in C++ # Aim -**_BuildCC_** aims to be an alternative to **Makefiles** while using the feature rich C++ language. +**_BuildCC_** aims to be an alternative to **Makefiles** while using the feature rich C++ language instead of a custom DSL. + +## Features + +- Complete flexibility for custom workflows and toolchains +- C++ language feature benefits and **debuggable build binaries** +- Optimized rebuilds through serialization. See [target.fbs schema](buildcc/schema/target.fbs) + - Can optimize for rebuilds by comparing the previous stored build with current build. + - Also see the [FAQ](#faq) for more details on Serialization +- Customizable for community plugins. More details provided in the [Community Plugin](#community-plugin) section. ## Pre-requisites @@ -12,22 +27,29 @@ Build C, C++ and ASM files in C++ - `C++17 filesystem` library support - `C++11 thread` library support - Third Party Libraries (See License below) - - Flatbuffers - - Taskflow - - CLI11 - - Tiny Process Library - - fmt - - spdlog + - Nlohmann::Json v3.11.2 + - Taskflow v3.1.0 + - CLI11 v2.1.0 + - Tiny Process Library v2.0.4 + - fmt v8.0.1 + - spdlog v1.9.2 + - CppUTest v4.0 + - Tl::Optional (master) # General Information -- A `compile` + `link` procedure is called a **Target** +- A one stage `input / output` procedure is called a **Generator** with a wide variety of use cases + - Single input creates single output + - Single input creates multiple outputs + - Multiple inputs create single output + - Multiple inputs creates multiple outputs +- A two stage `compile` and `link` procedure is called a **Target** - This means that Executables, StaticLibraries and DynamicLibraries are all categorized as Targets - - In the future C++20 modules can also be its own target dependending on compiler implementations + - In the future C++20 modules can also be its own target depending on compiler implementations - Every Target requires a complementary (and compatible) **Toolchain** - This ensures that cross compiling is very easy and explicit in nature. - Multiple toolchains can be _mixed_ in a single build file i.e we can generate targets using the GCC, Clang, MSVC and many other compilers **simultaneously**. -- The `compile_command` and `link_command` is fed to the `process/system` call to generate files. +- The `compile_command` (pch and object commands) and `link_command` (target command) is fed to the `process` call to invoke the `Toolchain`. - Each **Target** can depend on other targets efficiently through Parallel Programming using **Taskflow**. - Dependency between targets is explicitly mentioned through the Taskflow APIs - This has been made easier for the user through the `buildcc::Register` module. @@ -36,154 +58,97 @@ Build C, C++ and ASM files in C++ - Users can define their own custom arguments. - Argument passing has been made easy using the `buildcc::Args` module. -**Taskflow dependency for hybrid/simple example** -![Taskflow dependency](example/hybrid/simple/graph.PNG) -See also [Software Architecture](#software-architecture) +# Software Architecture -## Features +### Interface lib dependencies -- Complete flexibility for custom workflows and toolchains -- C++ language feature benefits and **debuggable build binaries** -- Optimized rebuilds through serialization. See [target.fbs schema](buildcc/lib/target/fbs/target.fbs) - - Can optimize for rebuilds by comparing the previous stored build with current build. - - See also [FAQ](#faq) -- Customizable for community plugins. More details provided in the `Community Plugin` section. +![BuildCC Interface library](doc/software_architecture/buildcc_interface_lib.PNG) -## Software Architecture +### Single lib dependencies -![Library dependency](doc/software_architecture/buildcc_core_dep.PNG) +![BuildCC Single library](doc/software_architecture/buildcc_single_lib.PNG) - See also [how to generate graphs using CMake](doc/software_architecture/generate_cmake_graphviz.md) -## Community Plugin - -- [x] [ClangCompileCommands](buildcc/plugins/clang_compile_commands.h) -- [ ] ClangFormat -- [ ] Target graph visualizer (through Taskflow) - -- `buildcc::base::Target` contains public getters that can be used to construct unique community plugins. -- Common tools and plugins would have first-party support in buildcc. -- All other tools and plugins can be maintained through individual developers. - -# User Guide - -Developers interested in using **_BuildCC_** - -## Build - -> NOTE: Currently, BuildCC needs to be built from source and bootstrapped using CMake. - -> I aim to bootstrap BuildCC into an executable to remove the dependency on CMake. +## Dependency Chart -- By default all the developer options are turned OFF. -- Only the `BUILDCC_INSTALL` option is turned on. +![Dependency Chart](doc/software_architecture/uml/dependency_graph.png) -```bash -# Generate your project -cmake -B [Build folder] -G [Generator] -cmake -B build -G Ninja +## State Diagram -# Build your project -cmake --build build -``` +### [Generator State Diagram](doc/software_architecture/uml/generator_tasks.png) -## Install +### [Target State Diagram](doc/software_architecture/uml/target_tasks.png) -```bash -# Manually -cd [build_folder] -sudo cmake --install . +> See also [how to create uml diagrams using VSCode](doc/software_architecture/create_uml_diagrams.md) -# Cpack generators -cpack --help - -# ZIP -cpack -G ZIP +## Community Plugin -# Executable -cpack -G NSIS -``` +- `buildcc::base::Generator`, `buildcc::base::TargetInfo` and `buildcc::base::Target` contains public getters that can be used to construct unique community plugins. +- Common tools and plugins would have first-party support in buildcc. +- All other tools and plugins can be maintained by individual developers. -> NOTE: On windows [NSIS](https://nsis.sourceforge.io/Main_Page) needs to be installed +**Current state of BuildCC supported plugins** -- Install the package and add to environment PATH -- As a starting point, go through the **gcc/AfterInstall** example and **Hybrid** examples -- For more details read the `examples` README to use buildcc in different situations +- [x] [ClangCompileCommands](buildcc/plugins/include/plugins/clang_compile_commands.h) +- [ ] [BuildCCFind](buildcc/plugins/include/plugins/buildcc_find.h) + - [x] Host system executable + - [ ] BuildCC Library + - [ ] BuildCC Plugin +- [ ] ClangFormat +- [ ] Target graph visualizer (through Taskflow) -## Examples +# Examples Contains **proof of concept** and **real world** [examples](example/README.md). -# Developer +## Visual hybrid example graphs -Developers interested in contributing to **_BuildCC_** - -## Build +**Taskflow dependency for hybrid/simple example** +![Hybrid Simple example](example/hybrid/simple/graph.PNG) -### CMakePresets (from Version 3.20) +- Build GCC and MSVC Targets simultaneously +- 1 C and 1 CPP example for both toolchains -- See `CMakePresets.json` for GCC, MSVC and Clang configurations -```bash -# Generating -cmake --list-presets -cmake --preset=[your_preset] +**Taskflow dependency for hybrid/pch example** +![Hybrid PCH example](example/hybrid/pch/graph.PNG) -# Building -cmake --build --list-presets -cmake --build --preset=[your_preset] +- Activate PCH for GCC and MSVC Targets +- 1 C and 1 CPP example for both toolchains -# Testing (ONLY supported on gcc) -ctest --preset=gcc_dev_all -``` +**Taskflow dependency for hybrid/dep_chaining example** +![Hybrid Dep Chain example](example/hybrid/dep_chaining/graph.PNG) -### Custom Targets +- Chain **Generator** with **Targets** for Dependency +- 1 C and 1 CPP example for both toolchains -```bash -# Run custom target using -cd [folder] -cmake --build . --target [custom_target] -``` +# User Guide -**Tools** -- cppcheck_static_analysis -- doxygen_documentation -- gcovr_coverage -- lcov_coverage +Developers interested in using **_BuildCC_** -**Examples** -- run_hybrid_simple_example_linux -- run_hybrid_simple_example_win -- run_hybrid_foolib_example_linux -- run_hybrid_foolib_example_win -- run_hybrid_externallib_example_linux -- run_hybrid_externallib_example_win -- run_hybrid_customtarget_example_linux -- run_hybrid_customtarget_example_win +- [Installation using CMake](doc/user/installation_using_cmake.md) -## Install +# Developer Guide -- See the **user installation** section above +Developers interested in contributing to **_BuildCC_** -- Read [Install target](buildcc/lib/target/cmake/target_install.cmake) +- [Project internal information](doc/developer/project_internals.md) -Basic Installation steps -- Install `TARGETS` -- Install `HEADER FILES` -- Export `CONFIG` +# FAQ -## Test +## Target -- Read [Mock env](buildcc/lib/env/CMakeLists.txt) -- Read [Mock target](buildcc/lib/target/cmake/mock_target.cmake) -- Read [Test path](buildcc/lib/target/test/path/CMakeLists.txt) -- Read [Test target](buildcc/lib/target/test/target/CMakeLists.txt) +- [How do I supply my own custom `compile_command` and `link_command` to targets?](doc/target/custom_commands.md) -# FAQ +## Serialization -- [Why has _this_ third-party library been chosen?](doc/faq/why_this_lib.md) +- [Understanding `path.fbs`](doc/serialization/path_fbs.md) +- [Understanding `generator.fbs`](doc/serialization/generator_fbs.md) +- [Understanding `target.fbs`](doc/serialization/target_fbs.md) -## Design +## Design/Reasoning +- [Why has _this_ third-party library been chosen?](doc/faq/why_this_lib.md) - [Why do you track _include directories_ and _header files_?](doc/faq/include_dir_vs_header_files.md) ## Miscellaneous @@ -206,11 +171,12 @@ _BuildCC_ is licensed under the Apache License, Version 2.0. See [LICENSE](LICEN > Developers who would like to suggest an alternative library, raise an issue with the **license** and **advantages** clearly outlined. -- [Fmtlib](https://github.com/fmtlib/fmt) (Formatting) [MIT License] [Header Only] -- [Spdlog](https://github.com/gabime/spdlog) (Logging) [MIT License] [Header Only] +- [Fmtlib](https://github.com/fmtlib/fmt) (Formatting) [MIT License] +- [Spdlog](https://github.com/gabime/spdlog) (Logging) [MIT License] - [Tiny Process Library](https://gitlab.com/eidheim/tiny-process-library) (Process handling) [MIT License] - [Taskflow](https://github.com/taskflow/taskflow) (Parallel Programming) [MIT License] [Header Only] - See also [3rd-Party](https://github.com/taskflow/taskflow/tree/master/3rd-party) used by Taskflow -- [Flatbuffers](https://github.com/google/flatbuffers) (Serialization) [Apache-2.0 License] +- [Nlohmann::Json](https://github.com/nlohmann/json) (JSON Serialization) [MIT License] [Header Only] - [CLI11](https://github.com/CLIUtils/CLI11) (Argument Parsing) [BSD-3-Clause License] [Header Only] - [CppUTest](https://github.com/cpputest/cpputest) (Unit Testing/Mocking) [BSD-3-Clause License] +- [Tl::Optional](https://github.com/TartanLlama/optional) (Optional support) [CC0-1.0 License] diff --git a/TODO.md b/TODO.md index 5d6a6e41..87a228cb 100644 --- a/TODO.md +++ b/TODO.md @@ -1,29 +1,91 @@ +# Versions + +# 0.1.1 + +Complete working proof of concept of the following + +- BuildCC library +- BuildCC bootstrap "script" files (Basic) +- BuildExe executable (Standalone) + +Contains the following working features + +**BuildCC** +- Supported plugin + - Clang Compile Commands +- Toolchain, Generator, TargetInfo and Target interfaces +- Specialized Toolchain for GCC, MSVC and MINGW +- Specialized Target for GCC, MSVC and MINGW + +**BuildExe** +- Immediate mode +- Script mode +- Local Package Manager with git + +## 0.1.2 + +- Serialization Interface +- Namespace changes + - Remove ``buildcc::env`` + - We should only have 3 namespaces ``buildcc``, ``buildcc::plugin`` and ``buildcc::internal`` +- Environment updates + - Remove ``buildcc::env`` + - Refactor free / static functions and variables into classes with static members and variables. For example. ``buildcc::env::init`` should become ``buildcc::Environment::Init`` +- Args and Register module updates + - Pch command from command line + - Make Register functions static. ``Register::Build`` + - Update ``CallbackIf``, ``Build`` and ``Test`` APIs for the ``state`` variable usage +- Unit testing and mocking for BuildExe + +## 0.1.3 + +- Make a common interface / internal library which contains all utility functions and libraries +- New generators + - Currently we only have a simple Generator which is similar to our FileIOGenerator (input -> subprocess commands -> outputs) + - Read the ``faq`` generators to make more varied and robust generators. + +## 0.1.4 + +- Config options updates as per Target requirements + - Update configs to use ``fmt::format`` with format specifiers for "{prefix}{value}{suffix}" for customizability. For example: `/D{preprocessor}` for msvc or `-D{preprocessor}` for gcc etc +- Target specialized clang + - Clang behaves differently depending on its backend + - Option 1: Consider adding more options to ``ToolchainId`` and different Clang specializations. For example: ``Target_gcc_clang`` or ``Target_msvc_clang`` or ``Target_mingw_clang`` etc + - Option 2: Consider making a ``Target_clang`` that changes behaviour as per the ``target_triple_architecture`` present in the ``toolchain`` + - What other flavours of clang are present? + +## 0.2.x + +- `Append*` APIs +- `Add*WithFormat` or `Append*WithFormat` APIs + +## Long Term goals + +- [Discussion] Supported plugin requirements by users +- [Discussion] Customizability requirements by users +- [Discussion] Target and Generator interfaces for standardization by compilers. (White paper) +- [Community Support] MacOS testing and CI/CD # Feature - [ ] Bootstrapping - - [x] Via CMake - - [ ] Via buildcc -- [x] Subprocess/Process - - Tiny Process Library - - Reproc - - Ninja Subprocess -- [ ] Command/Subprocess class to construct a specialized query - - Currently, `internal::command` uses `std::system` and command tokens are passed in through `std::vector` (no sanitization or security) - - This class must also be easy enough to be used by users to construct external commands. -- [ ] Plugin - ClangFormat -- [ ] Plugin - Graph Visualizer -- [ ] PrecompileHeader support + - CMake is used to create BuildCC + - We now create a BuildCC executable that creates BuildCC + - [ ] BuildCC bootstrap executable through CMake (Static Libraries during linkage) + - [ ] BuildCC bootstrap executable through CMake (Dynamic Libraries during linkage) + - [ ] BuildCC bootstrap executable through BuildCC bootstrap executable (similar to the CMake executable) - [ ] C++20 module support - Understand flags - Understand procedure for GCC, MSVC and Clang +- [ ] Plugin - BuildCCFind + - Find executable + - Find toolchain +- [ ] Plugin - ClangFormat +- [ ] Plugin - Graph Visualizer # User QOL -- [ ] Getter APIs for Target - [ ] Append Setter APIs for Target -- [ ] `find_program` option in Toolchain -- [ ] Custom executables stored in Toolchain # Developer Tools @@ -31,29 +93,28 @@ - Online documentation (read the docs) - Github pages - [ ] CI/CD -- [ ] Codecov -- [ ] Codacy + - [ ] Linux + - [x] GCC + - [ ] Clang + - [ ] Windows + - [x] MSVC + - [x] Clang + - [ ] MinGW + - [ ] MacOS +- [x] Codacy # Optimization -- [ ] `fs::path::string_type` conversion to `std::string` - - In windows `fs::path::string_type` is `std::wstring`, discuss potential pitfalls for conversion and storing as `std::string` -- [ ] Aggregated strings stored in Target vs `std::insert` on `std::vector` and `std::unordered_set` -- [ ] Handling exceptions and generating fatal exceptions - - Discuss different strategies for exceptions i.e throw, std::error_code etc -- [ ] `std::string` vs `std::string_view` usage -- [ ] Static library vs Shared Library when linking buildcc (See **Software Architecture** section) - - Static library linking is extremely slow on certain compilers -- [ ] Third party library optimization - - flatbuffers - - Do not create static/dynamic **fmt** or **spdlog** library, use INTERFACE header only library. We might need to write our own cmake script for INTERFACE and INSTALL. -- [ ] C++20 constexpr `std::string` and `std::vector` usage +- [ ] Speed vs Memory considerations + - Currently the project favours speed over memory usage +- [ ] `std::string` vs `std::string_view` vs `const char *` usage + - NOTE, We cannot convert between `std::string_view` and `const char *` which makes it harder to use `std::string_view` for certain APIs # Tests -- [ ] 100% Line Coverage - [ ] Improve Branch Coverage -- [ ] Benchmark example CMake vs BuildCC +- [ ] Profiling BuildCC using [Tracy](https://github.com/wolfpld/tracy) +- [ ] Speed comparison between CMake and BuildCC (Release) - [ ] Speed profiling `subprocess` vs `std::system` with gprof and qcachegrind - NOTE, Since we have Taskflow for parallel programming, we do not need to construct a multi-threaded subprocess. - Subprocess should typically replicate `std::system` functionality while offering better security. @@ -63,3 +124,4 @@ - [ ] Cross compiling - [ ] Debugging using VSCode - [ ] Debugging using GDB + - Check the [GDBFrontend](https://github.com/rohanrhu/gdb-frontend) project diff --git a/bootstrap/.gitignore b/bootstrap/.gitignore new file mode 100644 index 00000000..cf658897 --- /dev/null +++ b/bootstrap/.gitignore @@ -0,0 +1 @@ +*.dot diff --git a/bootstrap/CMakeLists.txt b/bootstrap/CMakeLists.txt new file mode 100644 index 00000000..924fa351 --- /dev/null +++ b/bootstrap/CMakeLists.txt @@ -0,0 +1,50 @@ +add_executable(buildcc_lib_bootstrap + main.buildcc.cpp +) +target_sources(buildcc_lib_bootstrap PRIVATE + src/build_nlohmann_json.cpp + src/build_cli11.cpp + src/build_fmtlib.cpp + src/build_spdlog.cpp + src/build_taskflow.cpp + src/build_tpl.cpp + src/build_tl_optional.cpp + + src/build_buildcc.cpp +) +target_include_directories(buildcc_lib_bootstrap PRIVATE + include +) +target_link_libraries(buildcc_lib_bootstrap PRIVATE buildcc) + +# TODO, Add this only if MINGW is used +# https://github.com/msys2/MINGW-packages/issues/2303 +# Similar issue when adding the Taskflow library +if (${MINGW}) + message(WARNING "-Wl,--allow-multiple-definition for MINGW") + target_link_options(buildcc_lib_bootstrap PRIVATE -Wl,--allow-multiple-definition) +endif() + +# Linux GCC +add_custom_target(run_buildcc_lib_bootstrap_linux_gcc + COMMAND buildcc_lib_bootstrap --help-all + COMMAND buildcc_lib_bootstrap --config ${CMAKE_CURRENT_SOURCE_DIR}/config_default.toml --config ${CMAKE_CURRENT_SOURCE_DIR}/config/toolchain_linux_gcc.toml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + VERBATIM USES_TERMINAL +) + +# Win GCC/MINGW +add_custom_target(run_buildcc_lib_bootstrap_win_gcc + COMMAND buildcc_lib_bootstrap --help-all + COMMAND buildcc_lib_bootstrap --config ${CMAKE_CURRENT_SOURCE_DIR}/config_default.toml --config ${CMAKE_CURRENT_SOURCE_DIR}/config/toolchain_win_gcc.toml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + VERBATIM USES_TERMINAL +) + +# Win MSVC +add_custom_target(run_buildcc_lib_bootstrap_win_msvc + COMMAND buildcc_lib_bootstrap --help-all + COMMAND buildcc_lib_bootstrap --config ${CMAKE_CURRENT_SOURCE_DIR}/config_default.toml --config ${CMAKE_CURRENT_SOURCE_DIR}/config/toolchain_win_msvc.toml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + VERBATIM USES_TERMINAL +) diff --git a/bootstrap/config/toolchain_linux_gcc.toml b/bootstrap/config/toolchain_linux_gcc.toml new file mode 100644 index 00000000..89ce7d40 --- /dev/null +++ b/bootstrap/config/toolchain_linux_gcc.toml @@ -0,0 +1,11 @@ +[toolchain.host] +build = true +test = true + +id = "gcc" +name = "x86_64-linux-gnu" +asm_compiler = "as" +c_compiler = "gcc" +cpp_compiler = "g++" +archiver = "ar" +linker = "ld" diff --git a/bootstrap/config/toolchain_win_gcc.toml b/bootstrap/config/toolchain_win_gcc.toml new file mode 100644 index 00000000..d078e06f --- /dev/null +++ b/bootstrap/config/toolchain_win_gcc.toml @@ -0,0 +1,11 @@ +[toolchain.host] +build = true +test = true + +id = "mingw" +name = "x86_64-w64-mingw32" +asm_compiler = "as" +c_compiler = "gcc" +cpp_compiler = "g++" +archiver = "ar" +linker = "ld" diff --git a/bootstrap/config/toolchain_win_msvc.toml b/bootstrap/config/toolchain_win_msvc.toml new file mode 100644 index 00000000..2fb80da5 --- /dev/null +++ b/bootstrap/config/toolchain_win_msvc.toml @@ -0,0 +1,11 @@ +[toolchain.host] +build = true +test = true + +id = "msvc" +name = "msvc_x64" +asm_compiler = "cl" +c_compiler = "cl" +cpp_compiler = "cl" +archiver = "lib" +linker = "link" diff --git a/bootstrap/config_default.toml b/bootstrap/config_default.toml new file mode 100644 index 00000000..a3134757 --- /dev/null +++ b/bootstrap/config_default.toml @@ -0,0 +1,5 @@ +root_dir=".." +build_dir="../_build_bootstrap" + +loglevel="trace" +clean=false diff --git a/bootstrap/include/bootstrap/build_buildcc.h b/bootstrap/include/bootstrap/build_buildcc.h new file mode 100644 index 00000000..bef6fdf3 --- /dev/null +++ b/bootstrap/include/bootstrap/build_buildcc.h @@ -0,0 +1,99 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BOOTSTRAP_BUILD_BUILDCC_H_ +#define BOOTSTRAP_BUILD_BUILDCC_H_ + +#include "buildcc.h" + +#include "build_cli11.h" +#include "build_fmtlib.h" +#include "build_nlohmann_json.h" +#include "build_spdlog.h" +#include "build_taskflow.h" +#include "build_tl_optional.h" +#include "build_tpl.h" + +namespace buildcc { + +void buildcc_cb(BaseTarget &target, const TargetInfo &nlohmann_json_ho, + const TargetInfo &fmt_ho, const TargetInfo &spdlog_ho, + const TargetInfo &cli11_ho, const TargetInfo &taskflow_ho, + const TargetInfo &tl_optional_ho, const BaseTarget &tpl); + +/** + * @brief + * + */ +class BuildBuildCC { +public: + // TargetInfo / Header Only + static constexpr const char *const kNlohmannJsonHoName = "nlohmann_json_ho"; + static constexpr const char *const kCli11HoName = "cli11_ho"; + static constexpr const char *const kFmtHoName = "fmtlib_ho"; + static constexpr const char *const kSpdlogHoName = "spdlog_ho"; + static constexpr const char *const kTaskflowHoName = "taskflow_ho"; + static constexpr const char *const kTlOptionalHoName = "tl_optional_ho"; + + // Executable + static constexpr const char *const kFlatcExeName = "flatc"; + + // Libraries + static constexpr const char *const kTplLibName = "libtpl"; + static constexpr const char *const kBuildccLibName = "libbuildcc"; + +public: + BuildBuildCC(const BaseToolchain &toolchain, const TargetEnv &env) + : toolchain_(toolchain), env_(env) { + Initialize(); + } + BuildBuildCC(const BuildBuildCC &) = delete; + + void Setup(const ArgToolchainState &state); + + // Getters + StaticTarget_generic &GetTpl() { + return storage_.Ref(kTplLibName); + } + StaticTarget_generic &GetBuildcc() { + return storage_.Ref(kBuildccLibName); + } + +private: + void Initialize(); + TargetInfo &GetNlohmannJsonHo() { + return storage_.Ref(kNlohmannJsonHoName); + } + TargetInfo &GetCli11Ho() { return storage_.Ref(kCli11HoName); } + TargetInfo &GetFmtHo() { return storage_.Ref(kFmtHoName); } + TargetInfo &GetSpdlogHo() { return storage_.Ref(kSpdlogHoName); } + TargetInfo &GetTaskflowHo() { + return storage_.Ref(kTaskflowHoName); + } + TargetInfo &GetTlOptionalHo() { + return storage_.Ref(kTlOptionalHoName); + } + +private: + const BaseToolchain &toolchain_; + TargetEnv env_; + + ScopedStorage storage_; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/toolchains/toolchain_gcc.h b/bootstrap/include/bootstrap/build_cli11.h similarity index 65% rename from buildcc/toolchains/toolchain_gcc.h rename to bootstrap/include/bootstrap/build_cli11.h index d514d41e..44fdc96a 100644 --- a/buildcc/toolchains/toolchain_gcc.h +++ b/bootstrap/include/bootstrap/build_cli11.h @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,18 +14,14 @@ * limitations under the License. */ -#ifndef TOOLCHAINS_TOOLCHAIN_GCC_H_ -#define TOOLCHAINS_TOOLCHAIN_GCC_H_ +#ifndef BOOTSTRAP_BUILD_CLI11_H_ +#define BOOTSTRAP_BUILD_CLI11_H_ -#include "toolchain.h" +#include "buildcc.h" namespace buildcc { -class Toolchain_gcc : public base::Toolchain { -public: - Toolchain_gcc() : Toolchain("gcc", "as", "gcc", "g++", "ar", "ld") {} - Toolchain_gcc(const Toolchain_gcc &gcc) = delete; -}; +void cli11_ho_cb(TargetInfo &info); } // namespace buildcc diff --git a/buildcc/toolchains/toolchain_msvc.h b/bootstrap/include/bootstrap/build_flatbuffers.h similarity index 64% rename from buildcc/toolchains/toolchain_msvc.h rename to bootstrap/include/bootstrap/build_flatbuffers.h index 6c3496dc..e61d2430 100644 --- a/buildcc/toolchains/toolchain_msvc.h +++ b/bootstrap/include/bootstrap/build_flatbuffers.h @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,18 +14,15 @@ * limitations under the License. */ -#ifndef TOOLCHAINS_TOOLCHAIN_MSVC_H_ -#define TOOLCHAINS_TOOLCHAIN_MSVC_H_ +#ifndef BOOTSTRAP_BUILD_FLATBUFFERS_H_ +#define BOOTSTRAP_BUILD_FLATBUFFERS_H_ -#include "toolchain.h" +#include "buildcc.h" namespace buildcc { -class Toolchain_msvc : public base::Toolchain { -public: - Toolchain_msvc() : Toolchain("msvc", "cl", "cl", "cl", "lib", "link") {} - Toolchain_msvc(const Toolchain_msvc &gcc) = delete; -}; +void build_flatc_exe_cb(BaseTarget &target); +void flatbuffers_ho_cb(TargetInfo &info); } // namespace buildcc diff --git a/buildcc/targets/gcc/executable_target_gcc.h b/bootstrap/include/bootstrap/build_fmtlib.h similarity index 55% rename from buildcc/targets/gcc/executable_target_gcc.h rename to bootstrap/include/bootstrap/build_fmtlib.h index e9539e30..1c3a978a 100644 --- a/buildcc/targets/gcc/executable_target_gcc.h +++ b/bootstrap/include/bootstrap/build_fmtlib.h @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,21 +14,14 @@ * limitations under the License. */ -#ifndef TARGETS_GCC_EXECUTABLE_TARGET_GCC_H_ -#define TARGETS_GCC_EXECUTABLE_TARGET_GCC_H_ +#ifndef BOOTSTRAP_BUILD_FMTLIB_H_ +#define BOOTSTRAP_BUILD_FMTLIB_H_ -#include "target.h" +#include "buildcc.h" namespace buildcc { -class ExecutableTarget_gcc : public base::Target { -public: - ExecutableTarget_gcc( - const std::string &name, const base::Toolchain &toolchain, - const std::filesystem::path &target_path_relative_to_root) - : Target(name, base::TargetType::Executable, toolchain, - target_path_relative_to_root) {} -}; +void fmt_ho_cb(TargetInfo &info); } // namespace buildcc diff --git a/bootstrap/include/bootstrap/build_nlohmann_json.h b/bootstrap/include/bootstrap/build_nlohmann_json.h new file mode 100644 index 00000000..135275f0 --- /dev/null +++ b/bootstrap/include/bootstrap/build_nlohmann_json.h @@ -0,0 +1,28 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BOOTSTRAP_BUILD_NLOHMANN_JSON_H_ +#define BOOTSTRAP_BUILD_NLOHMANN_JSON_H_ + +#include "buildcc.h" + +namespace buildcc { + +void nlohmann_json_ho_cb(TargetInfo &info); + +} // namespace buildcc + +#endif diff --git a/bootstrap/include/bootstrap/build_spdlog.h b/bootstrap/include/bootstrap/build_spdlog.h new file mode 100644 index 00000000..4b796d7d --- /dev/null +++ b/bootstrap/include/bootstrap/build_spdlog.h @@ -0,0 +1,28 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BOOTSTRAP_BUILD_SPDLOG_H_ +#define BOOTSTRAP_BUILD_SPDLOG_H_ + +#include "buildcc.h" + +namespace buildcc { + +void spdlog_ho_cb(TargetInfo &info); + +} // namespace buildcc + +#endif diff --git a/bootstrap/include/bootstrap/build_taskflow.h b/bootstrap/include/bootstrap/build_taskflow.h new file mode 100644 index 00000000..1335ee77 --- /dev/null +++ b/bootstrap/include/bootstrap/build_taskflow.h @@ -0,0 +1,28 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BOOTSTRAP_BUILD_TASKFLOW_H_ +#define BOOTSTRAP_BUILD_TASKFLOW_H_ + +#include "buildcc.h" + +namespace buildcc { + +void taskflow_ho_cb(TargetInfo &info); + +} // namespace buildcc + +#endif diff --git a/bootstrap/include/bootstrap/build_tl_optional.h b/bootstrap/include/bootstrap/build_tl_optional.h new file mode 100644 index 00000000..4ca39e01 --- /dev/null +++ b/bootstrap/include/bootstrap/build_tl_optional.h @@ -0,0 +1,28 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BOOTSTRAP_BUILD_TL_OPTIONAL_H_ +#define BOOTSTRAP_BUILD_TL_OPTIONAL_H_ + +#include "buildcc.h" + +namespace buildcc { + +void tl_optional_ho_cb(TargetInfo &info); + +} // namespace buildcc + +#endif diff --git a/bootstrap/include/bootstrap/build_tpl.h b/bootstrap/include/bootstrap/build_tpl.h new file mode 100644 index 00000000..9d00cc5e --- /dev/null +++ b/bootstrap/include/bootstrap/build_tpl.h @@ -0,0 +1,28 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BOOTSTRAP_BUILD_TPL_H_ +#define BOOTSTRAP_BUILD_TPL_H_ + +#include "buildcc.h" + +namespace buildcc { + +void tpl_cb(BaseTarget &target); + +} // namespace buildcc + +#endif diff --git a/bootstrap/main.buildcc.cpp b/bootstrap/main.buildcc.cpp new file mode 100644 index 00000000..4eafe255 --- /dev/null +++ b/bootstrap/main.buildcc.cpp @@ -0,0 +1,88 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "buildcc.h" + +#include "bootstrap/build_cli11.h" +#include "bootstrap/build_fmtlib.h" +#include "bootstrap/build_spdlog.h" +#include "bootstrap/build_taskflow.h" +#include "bootstrap/build_tl_optional.h" +#include "bootstrap/build_tpl.h" + +#include "bootstrap/build_buildcc.h" + +using namespace buildcc; + +static void clean_cb(); + +static void hybrid_simple_example_cb(BaseTarget &target, + const BaseTarget &libbuildcc); + +int main(int argc, char **argv) { + ArgToolchain custom_toolchain_arg; + Args::Init() + .AddToolchain("host", "Host Toolchain", custom_toolchain_arg) + .Parse(argc, argv); + + Reg::Init(); + Reg::Call(Args::Clean()).Func(clean_cb); + + auto &toolchain = custom_toolchain_arg.ConstructToolchain(); + BuildBuildCC buildcc( + toolchain, TargetEnv(Project::GetRootDir(), Project::GetBuildDir())); + auto &buildcc_lib = buildcc.GetBuildcc(); + + ExecutableTarget_generic buildcc_hybrid_simple_example( + "buildcc_hybrid_simple_example", toolchain, "example/hybrid/simple"); + Reg::Toolchain(custom_toolchain_arg.state) + .Func([&]() { toolchain.Verify(); }) + .BuildPackage(buildcc) + .Build(hybrid_simple_example_cb, buildcc_hybrid_simple_example, + buildcc_lib) + .Dep(buildcc_hybrid_simple_example, buildcc_lib); + + // Runners + Reg::Run(); + + // - Clang Compile Commands + plugin::ClangCompileCommands({&buildcc_lib}).Generate(); + + // - Plugin Graph + std::string output = Reg::GetTaskflow().dump(); + const bool saved = env::save_file("graph.dot", output, false); + env::assert_fatal(saved, "Could not save graph.dot file"); + + return 0; +} + +static void clean_cb() {} + +static void hybrid_simple_example_cb(BaseTarget &target, + const BaseTarget &libbuildcc) { + target.AddLibDep(libbuildcc); + target.Insert(libbuildcc, { + SyncOption::PreprocessorFlags, + SyncOption::CppCompileFlags, + SyncOption::LinkFlags, + SyncOption::HeaderFiles, + SyncOption::IncludeDirs, + SyncOption::LibDeps, + SyncOption::ExternalLibDeps, + }); + target.AddSource("build.cpp"); + target.Build(); +} diff --git a/bootstrap/src/build_buildcc.cpp b/bootstrap/src/build_buildcc.cpp new file mode 100644 index 00000000..38c2072c --- /dev/null +++ b/bootstrap/src/build_buildcc.cpp @@ -0,0 +1,252 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bootstrap/build_buildcc.h" + +namespace buildcc { + +void buildcc_cb(BaseTarget &target, const TargetInfo &nlohmann_json_ho, + const TargetInfo &fmt_ho, const TargetInfo &spdlog_ho, + const TargetInfo &cli11_ho, const TargetInfo &taskflow_ho, + const TargetInfo &tl_optional_ho, const BaseTarget &tpl) { + // NOTE, Build as single lib + target.AddIncludeDir("", true); + + // ENV + target.GlobSources("lib/env/src"); + target.AddIncludeDir("lib/env/include"); + target.GlobHeaders("lib/env/include/env"); + + // SCHEMA + target.GlobSources("schema/src"); + target.AddIncludeDir("schema/include"); + target.GlobHeaders("schema/include/schema"); + target.GlobHeaders("schema/include/schema/interface"); + + // TOOLCHAIN + target.GlobSources("lib/toolchain/src/api"); + target.GlobSources("lib/toolchain/src/common"); + target.GlobSources("lib/toolchain/src/toolchain"); + target.AddIncludeDir("lib/toolchain/include"); + target.GlobHeaders("lib/toolchain/include/toolchain"); + target.GlobHeaders("lib/toolchain/include/toolchain/api"); + target.GlobHeaders("lib/toolchain/include/toolchain/common"); + + // TARGET + target.GlobSources("lib/target/src/common"); + target.GlobSources("lib/target/src/custom_generator"); + target.GlobSources("lib/target/src/generator"); + target.GlobSources("lib/target/src/api"); + target.GlobSources("lib/target/src/target_info"); + target.GlobSources("lib/target/src/target"); + target.GlobSources("lib/target/src/target/friend"); + + target.AddIncludeDir("lib/target/include"); + target.GlobHeaders("lib/target/include/target"); + target.GlobHeaders("lib/target/include/target/api"); + target.GlobHeaders("lib/target/include/target/common"); + target.GlobHeaders("lib/target/include/target/friend"); + target.GlobHeaders("lib/target/include/target/interface"); + + // ARGS + target.GlobSources("lib/args/src"); + target.AddIncludeDir("lib/args/include"); + target.GlobHeaders("lib/args/include/args"); + + // Specialized Toolchains + target.GlobSources("toolchains/src"); + target.AddIncludeDir("toolchains/include"); + target.GlobHeaders("toolchains/include/toolchains"); + + // Specialized Targets + target.AddIncludeDir("targets/include"); + target.GlobHeaders("targets/include/targets"); + + // Plugins + target.GlobSources("plugins/src"); + target.AddIncludeDir("plugins/include"); + target.GlobHeaders("plugins/include/plugins"); + + // Third Party libraries + + const std::initializer_list kInsertOptions{ + SyncOption::IncludeDirs, + SyncOption::HeaderFiles, + }; + + // NLOHMANN JSON HO + target.Insert(nlohmann_json_ho, kInsertOptions); + + // FMT HO + target.Insert(fmt_ho, kInsertOptions); + + // SPDLOG HO + target.Insert(spdlog_ho, kInsertOptions); + + // CLI11 HO + target.Insert(cli11_ho, kInsertOptions); + + // TASKFLOW HO + target.Insert(taskflow_ho, kInsertOptions); + + // TL OPTIONAL HO + target.Insert(tl_optional_ho, kInsertOptions); + + // TPL LIB + target.AddLibDep(tpl); + target.Insert(tpl, kInsertOptions); + + if constexpr (env::is_win()) { + // TODO, Clang + switch (target.GetToolchain().GetId()) { + case ToolchainId::MinGW: { + target.AddPreprocessorFlag("-DFMT_HEADER_ONLY=1"); + target.AddPreprocessorFlag("-DSPDLOG_FMT_EXTERNAL"); + // For MINGW + target.AddLinkFlag("-Wl,--allow-multiple-definition"); + } break; + case ToolchainId::Msvc: { + target.AddPreprocessorFlag("/DFMT_HEADER_ONLY=1"); + target.AddPreprocessorFlag("/DSPDLOG_FMT_EXTERNAL"); + } break; + default: + break; + } + } + + if constexpr (env::is_linux() || env::is_unix() || env::is_clang()) { + // TODO, Clang + switch (target.GetToolchain().GetId()) { + case ToolchainId::Gcc: { + target.AddPreprocessorFlag("-DFMT_HEADER_ONLY=1"); + target.AddPreprocessorFlag("-DSPDLOG_FMT_EXTERNAL"); + target.AddLibDep("-lpthread"); + } break; + default: + break; + } + } + + // TODO, Other OS's + + target.Build(); +} + +// TODO, Shift this inside BuildBuildcc class if required +// TODO, Add this to options +static void global_flags_cb(TargetInfo &global_info, + const BaseToolchain &toolchain) { + // TODO, Clang + switch (toolchain.GetId()) { + case ToolchainId::Gcc: + case ToolchainId::MinGW: + global_info.AddCppCompileFlag("-std=c++17"); + global_info.AddCppCompileFlag("-Os"); + global_info.AddCppCompileFlag("-Wall"); + global_info.AddCppCompileFlag("-Wextra"); + global_info.AddCppCompileFlag("-Werror"); + break; + case ToolchainId::Msvc: + global_info.AddPreprocessorFlag("/D_CRT_SECURE_NO_WARNINGS"); + global_info.AddCppCompileFlag("/std:c++17"); + global_info.AddCppCompileFlag("/Ot"); + global_info.AddCppCompileFlag("/W4"); + global_info.AddCppCompileFlag("/WX"); + default: + break; + } +} + +void BuildBuildCC::Initialize() { + // Nlohmann json HO lib + (void)storage_.Add( + kNlohmannJsonHoName, toolchain_, + TargetEnv(env_.GetTargetRootDir() / "third_party" / "json", + env_.GetTargetBuildDir())); + + // CLI11 HO lib + (void)storage_.Add( + kCli11HoName, toolchain_, + TargetEnv(env_.GetTargetRootDir() / "third_party" / "CLI11", + env_.GetTargetBuildDir())); + + // fmt HO lib + (void)storage_.Add( + kFmtHoName, toolchain_, + TargetEnv(env_.GetTargetRootDir() / "third_party" / "fmt", + env_.GetTargetBuildDir())); + + // spdlog HO lib + (void)storage_.Add( + kSpdlogHoName, toolchain_, + TargetEnv(env_.GetTargetRootDir() / "third_party" / "spdlog", + env_.GetTargetBuildDir())); + + // taskflow HO lib + (void)storage_.Add( + kTaskflowHoName, toolchain_, + TargetEnv(env_.GetTargetRootDir() / "third_party" / "taskflow", + env_.GetTargetBuildDir())); + + // tl optional HO lib + (void)storage_.Add( + kTlOptionalHoName, toolchain_, + TargetEnv(env_.GetTargetRootDir() / "third_party" / "tl_optional", + env_.GetTargetBuildDir())); + + // Tiny-process-library lib + // TODO, Make this a generic selection between StaticTarget and + // DynamicTarget + (void)storage_.Add( + kTplLibName, kTplLibName, toolchain_, + TargetEnv(env_.GetTargetRootDir() / "third_party" / + "tiny-process-library", + env_.GetTargetBuildDir())); + + // BuildCC lib + // TODO, Make this a generic selection between StaticTarget and + // DynamicTarget + (void)storage_.Add( + kBuildccLibName, kBuildccLibName, toolchain_, + TargetEnv(env_.GetTargetRootDir() / "buildcc", env_.GetTargetBuildDir())); +} + +void BuildBuildCC::Setup(const ArgToolchainState &state) { + auto &nlohmann_json_ho_lib = GetNlohmannJsonHo(); + auto &cli11_ho_lib = GetCli11Ho(); + auto &fmt_ho_lib = GetFmtHo(); + auto &spdlog_ho_lib = GetSpdlogHo(); + auto &taskflow_ho_lib = GetTaskflowHo(); + auto &tl_optional_ho_lib = GetTlOptionalHo(); + auto &tpl_lib = GetTpl(); + auto &buildcc_lib = GetBuildcc(); + Reg::Toolchain(state) + .Func(nlohmann_json_ho_cb, nlohmann_json_ho_lib) + .Func(cli11_ho_cb, cli11_ho_lib) + .Func(fmt_ho_cb, fmt_ho_lib) + .Func(spdlog_ho_cb, spdlog_ho_lib) + .Func(taskflow_ho_cb, taskflow_ho_lib) + .Func(tl_optional_ho_cb, tl_optional_ho_lib) + .Func(global_flags_cb, tpl_lib, toolchain_) + .Build(tpl_cb, tpl_lib) + .Func(global_flags_cb, buildcc_lib, toolchain_) + .Build(buildcc_cb, buildcc_lib, nlohmann_json_ho_lib, fmt_ho_lib, + spdlog_ho_lib, cli11_ho_lib, taskflow_ho_lib, tl_optional_ho_lib, + tpl_lib) + .Dep(buildcc_lib, tpl_lib); +} + +} // namespace buildcc diff --git a/bootstrap/src/build_cli11.cpp b/bootstrap/src/build_cli11.cpp new file mode 100644 index 00000000..13d4d2f9 --- /dev/null +++ b/bootstrap/src/build_cli11.cpp @@ -0,0 +1,26 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bootstrap/build_cli11.h" + +namespace buildcc { + +void cli11_ho_cb(TargetInfo &info) { + info.AddIncludeDir("include"); + info.GlobHeaders("include/CLI"); +} + +} // namespace buildcc diff --git a/bootstrap/src/build_flatbuffers.cpp b/bootstrap/src/build_flatbuffers.cpp new file mode 100644 index 00000000..82f47af5 --- /dev/null +++ b/bootstrap/src/build_flatbuffers.cpp @@ -0,0 +1,111 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bootstrap/build_flatbuffers.h" + +namespace { + +const std::vector kFlatcSources{ + "code_generators.cpp", + "flatc_main.cpp", + "flatc.cpp", + "idl_gen_cpp.cpp", + "idl_gen_csharp.cpp", + "idl_gen_dart.cpp", + "idl_gen_fbs.cpp", + "idl_gen_go.cpp", + "idl_gen_grpc.cpp", + "idl_gen_java.cpp", + "idl_gen_json_schema.cpp", + "idl_gen_kotlin.cpp", + "idl_gen_lobster.cpp", + "idl_gen_lua.cpp", + "idl_gen_php.cpp", + "idl_gen_python.cpp", + "idl_gen_rust.cpp", + "idl_gen_swift.cpp", + "idl_gen_text.cpp", + "idl_gen_ts.cpp", + "idl_parser.cpp", + "reflection.cpp", + "util.cpp", +}; + +const std::vector kFlatcPreprocessorFlags{ + "-DFLATBUFFERS_LOCALE_INDEPENDENT=0", + "-DNDEBUG", +}; + +const std::vector kFlatcGccCppCompileFlags{ + "-pedantic", "-Werror=shadow", + "-faligned-new", "-Werror=implicit-fallthrough=2", + "-Wunused-result", "-Werror=unused-result", + "-Wunused-parameter", "-Werror=unused-parameter", + "-fsigned-char", "-Wold-style-cast", + "-Winvalid-pch", +}; + +const std::vector kFlatcMsvcCppCompileFlags{ + "/MP", "/MT", "/GS", "/GR", + "/fp:precise", "/Zc:wchar_t", "/Zc:forScope", "/Zc:inline", +}; + +} // namespace + +namespace buildcc { + +void build_flatc_exe_cb(BaseTarget &target) { + std::for_each(kFlatcSources.cbegin(), kFlatcSources.cend(), + [&](const auto &s) { target.AddSource(s, "src"); }); + target.GlobSources("grpc/src/compiler"); + + target.AddIncludeDir("include"); + target.AddIncludeDir("grpc"); + + target.GlobHeaders("include/flatbuffers"); + target.GlobHeaders("grpc/src/compiler"); + + std::for_each(kFlatcPreprocessorFlags.cbegin(), + kFlatcPreprocessorFlags.cend(), + [&](const auto &f) { target.AddPreprocessorFlag(f); }); + + switch (target.GetToolchain().GetId()) { + case ToolchainId::Gcc: + case ToolchainId::MinGW: + std::for_each(kFlatcGccCppCompileFlags.cbegin(), + kFlatcGccCppCompileFlags.cend(), + [&](const auto &f) { target.AddCppCompileFlag(f); }); + break; + case ToolchainId::Msvc: + std::for_each(kFlatcMsvcCppCompileFlags.cbegin(), + kFlatcMsvcCppCompileFlags.cend(), + [&](const auto &f) { target.AddCppCompileFlag(f); }); + break; + case ToolchainId::Clang: + break; + default: + break; + } + + target.Build(); +} + +void flatbuffers_ho_cb(TargetInfo &info) { + info.AddIncludeDir("include"); + info.GlobHeaders("include/flatbuffers"); +} + +} // namespace buildcc diff --git a/bootstrap/src/build_fmtlib.cpp b/bootstrap/src/build_fmtlib.cpp new file mode 100644 index 00000000..d19d9f6d --- /dev/null +++ b/bootstrap/src/build_fmtlib.cpp @@ -0,0 +1,26 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bootstrap/build_fmtlib.h" + +namespace buildcc { + +void fmt_ho_cb(TargetInfo &info) { + info.AddIncludeDir("include"); + info.GlobHeaders("include/fmt"); +} + +} // namespace buildcc diff --git a/bootstrap/src/build_nlohmann_json.cpp b/bootstrap/src/build_nlohmann_json.cpp new file mode 100644 index 00000000..7bf5ce17 --- /dev/null +++ b/bootstrap/src/build_nlohmann_json.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bootstrap/build_nlohmann_json.h" + +namespace buildcc { + +void nlohmann_json_ho_cb(TargetInfo &info) { + info.AddIncludeDir("include"); + info.GlobHeaders("include/nlohmann"); + info.GlobHeaders("include/nlohmann/thirdparty/hedley"); + info.GlobHeaders("include/nlohmann/detail"); + info.GlobHeaders("include/nlohmann/detail/conversions"); + info.GlobHeaders("include/nlohmann/detail/input"); + info.GlobHeaders("include/nlohmann/detail/iterators"); + info.GlobHeaders("include/nlohmann/detail/meta"); + info.GlobHeaders("include/nlohmann/detail/meta/call_std"); + info.GlobHeaders("include/nlohmann/detail/output"); +} + +} // namespace buildcc diff --git a/bootstrap/src/build_spdlog.cpp b/bootstrap/src/build_spdlog.cpp new file mode 100644 index 00000000..2636c512 --- /dev/null +++ b/bootstrap/src/build_spdlog.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bootstrap/build_spdlog.h" + +namespace buildcc { + +void spdlog_ho_cb(TargetInfo &info) { + info.AddIncludeDir("include"); + info.GlobHeaders("include/spdlog"); + info.GlobHeaders("include/spdlog/cfg"); + info.GlobHeaders("include/spdlog/details"); + info.GlobHeaders("include/spdlog/fmt"); + info.GlobHeaders("include/spdlog/sinks"); +} + +} // namespace buildcc diff --git a/bootstrap/src/build_taskflow.cpp b/bootstrap/src/build_taskflow.cpp new file mode 100644 index 00000000..4852235a --- /dev/null +++ b/bootstrap/src/build_taskflow.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bootstrap/build_taskflow.h" + +namespace buildcc { + +void taskflow_ho_cb(TargetInfo &info) { + info.AddIncludeDir(""); + info.GlobHeaders("taskflow"); + info.GlobHeaders("taskflow/core"); + info.GlobHeaders("taskflow/core/algorithm"); + info.GlobHeaders("taskflow/cuda"); + info.GlobHeaders("taskflow/cuda/cuda_algorithm"); + info.GlobHeaders("taskflow/dsl"); + info.GlobHeaders("taskflow/sycl"); + info.GlobHeaders("taskflow/sycl/sycl_algorithm"); + info.GlobHeaders("taskflow/utility"); +} + +} // namespace buildcc diff --git a/bootstrap/src/build_tl_optional.cpp b/bootstrap/src/build_tl_optional.cpp new file mode 100644 index 00000000..3ac8926c --- /dev/null +++ b/bootstrap/src/build_tl_optional.cpp @@ -0,0 +1,26 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bootstrap/build_tl_optional.h" + +namespace buildcc { + +void tl_optional_ho_cb(TargetInfo &info) { + info.AddIncludeDir("include"); + info.GlobHeaders("include/tl"); +} + +} // namespace buildcc diff --git a/bootstrap/src/build_tpl.cpp b/bootstrap/src/build_tpl.cpp new file mode 100644 index 00000000..847161b1 --- /dev/null +++ b/bootstrap/src/build_tpl.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bootstrap/build_tpl.h" + +namespace buildcc { + +void tpl_cb(BaseTarget &target) { + target.AddSource("process.cpp"); + target.AddIncludeDir(""); + target.AddHeader("process.hpp"); + + if constexpr (env::is_win()) { + target.AddSource("process_win.cpp"); + } + + if constexpr (env::is_linux() || env::is_unix() || env::is_clang()) { + target.AddSource("process_unix.cpp"); + } + + target.Build(); +} + +} // namespace buildcc diff --git a/buildcc/CMakeLists.txt b/buildcc/CMakeLists.txt index 9254aa16..074288d2 100644 --- a/buildcc/CMakeLists.txt +++ b/buildcc/CMakeLists.txt @@ -2,6 +2,35 @@ set(BUILDCC_INSTALL_LIB_PREFIX "lib/cmake") set(BUILDCC_INSTALL_HEADER_PREFIX "include/buildcc") +if(${BUILDCC_BUILD_AS_SINGLE_LIB}) + add_library(buildcc STATIC + buildcc.h + ) + target_include_directories(buildcc PUBLIC + $ + $ + ) + target_link_libraries(buildcc PUBLIC + fmt::fmt + tl::optional + nlohmann_json::nlohmann_json + Taskflow + CLI11::CLI11 + ) + target_link_libraries(buildcc PRIVATE + spdlog::spdlog + tiny-process-library::tiny-process-library + ) + target_compile_options(buildcc PRIVATE ${BUILD_COMPILE_FLAGS}) + target_link_options(buildcc PRIVATE ${BUILD_LINK_FLAGS}) + if(${BUILDCC_PRECOMPILE_HEADERS}) + target_precompile_headers(buildcc INTERFACE buildcc.h) + endif() +endif() + +# Schema +add_subdirectory(schema) + # Environment add_subdirectory(lib/env) @@ -11,45 +40,46 @@ add_subdirectory(toolchains) # Targets add_subdirectory(lib/target) -add_subdirectory(targets/gcc) -add_subdirectory(targets/msvc) +add_subdirectory(targets) + +# Buildcc libs +add_subdirectory(lib/args) # First party plugins add_subdirectory(plugins) -m_clangtidy("buildcc") -add_library(buildcc STATIC) -target_sources(buildcc PRIVATE - src/args.cpp - src/register.cpp -) -target_include_directories(buildcc PUBLIC - $ - $ -) -target_link_libraries(buildcc PUBLIC - # - CLI11::CLI11 - - # - target - - toolchain_specialized - - # TODO, Add specialized targets - # GCC, Clang, MSVC - target_gcc - target_msvc - - plugins -) +if (${BUILDCC_BUILD_AS_INTERFACE}) + add_library(buildcc_i INTERFACE + buildcc.h + ) + target_include_directories(buildcc_i INTERFACE + $ + $ + ) + target_link_libraries(buildcc_i INTERFACE + target + args + plugins + toolchain_specialized + target_specialized + ) + if(${BUILDCC_PRECOMPILE_HEADERS}) + target_precompile_headers(buildcc_i INTERFACE buildcc.h) + endif() +endif() if (${BUILDCC_INSTALL}) - install(TARGETS buildcc DESTINATION lib EXPORT buildccConfig) + if(${BUILDCC_BUILD_AS_INTERFACE}) + install(TARGETS buildcc_i DESTINATION lib EXPORT buildcc_iConfig) + install(EXPORT buildcc_iConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/buildcc_i") + endif() + + if(${BUILDCC_BUILD_AS_SINGLE_LIB}) + install(TARGETS buildcc DESTINATION lib EXPORT buildccConfig) + install(EXPORT buildccConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/buildcc") + endif() + install(FILES - ${CMAKE_CURRENT_SOURCE_DIR}/include/buildcc.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/args.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/register.h + ${CMAKE_CURRENT_SOURCE_DIR}/buildcc.h DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}") - install(EXPORT buildccConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/buildcc") endif() diff --git a/buildcc/buildcc.h b/buildcc/buildcc.h new file mode 100644 index 00000000..a48551cb --- /dev/null +++ b/buildcc/buildcc.h @@ -0,0 +1,60 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BUILDCC_BUILDCC_H_ +#define BUILDCC_BUILDCC_H_ + +// clang-format off + +// Core persistent environment +#include "env/env.h" +#include "env/assert_fatal.h" +#include "env/logging.h" +#include "env/host_os.h" +#include "env/host_compiler.h" +#include "env/util.h" +#include "env/command.h" +#include "env/storage.h" + +// Base +#include "toolchain/toolchain.h" +#include "target/custom_generator.h" +#include "target/file_generator.h" +#include "target/template_generator.h" +#include "target/target_info.h" +#include "target/target.h" + +// Specialized Toolchain +#include "toolchains/toolchain_specialized.h" + +// Specialized Targets +#include "targets/target_gcc.h" +#include "targets/target_msvc.h" +#include "targets/target_mingw.h" +#include "targets/target_generic.h" +#include "targets/target_custom.h" + +// TODO, Add more specialized targets here + +// Plugins +#include "plugins/clang_compile_commands.h" +#include "plugins/buildcc_find.h" + +// BuildCC Modules +#include "args/args.h" +#include "args/register.h" + +#endif diff --git a/buildcc/include/args.h b/buildcc/include/args.h deleted file mode 100644 index cd78dd82..00000000 --- a/buildcc/include/args.h +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef BUILDCC_INCLUDE_ARGS_H_ -#define BUILDCC_INCLUDE_ARGS_H_ - -#include - -#include "logging.h" - -#include "CLI/CLI.hpp" - -namespace fs = std::filesystem; - -namespace buildcc { - -class Args { -public: - struct Toolchain { - bool build = false; - bool test = false; - }; - -public: - Args() { Initialize(); } - Args(const Args &) = delete; - - void Parse(int argc, char **argv); - - // TODO, Check if these are necessary - CLI::App &Ref() { return app_; } - const CLI::App &ConstRef() const { return app_; } - - // Setters - void AddCustomToolchain(const std::string &name, - const std::string &description, - Toolchain &custom_toolchain_arg); - - // Getters - bool Clean() const { return clean_; } - env::LogLevel GetLogLevel() const { return loglevel_; } - - const fs::path &GetProjectRootDir() const { return project_root_dir_; } - const fs::path &GetProjectBuildDir() const { return project_build_dir_; } - const Toolchain &GetGccToolchain() const { return gcc_toolchain_; } - const Toolchain &GetMsvcToolchain() const { return msvc_toolchain_; } - -private: - void Initialize(); - - void RootArgs(); - void ToolchainArgs(); - void AddToolchain(const std::string &name, const std::string &description, - const std::string &group, Toolchain &toolchain_arg); - -private: - CLI::App app_{"BuildCC buildsystem"}; - - bool clean_{false}; - env::LogLevel loglevel_{env::LogLevel::Info}; - const std::map loglevel_map_{ - {"Trace", env::LogLevel::Trace}, - {"Debug", env::LogLevel::Debug}, - {"Info", env::LogLevel::Info}, - {"Warning", env::LogLevel::Warning}, - {"Critical", env::LogLevel::Critical}, - }; - - // directory - fs::path project_root_dir_{""}; - fs::path project_build_dir_{"_internal"}; - - // toolchain - Toolchain gcc_toolchain_{false, false}; - Toolchain msvc_toolchain_{false, false}; - - // Internal - CLI::App *toolchain_{nullptr}; -}; - -} // namespace buildcc - -#endif diff --git a/buildcc/include/buildcc.h b/buildcc/include/buildcc.h deleted file mode 100644 index 257f5855..00000000 --- a/buildcc/include/buildcc.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef BUILDCC_INCLUDE_BUILDCC_H_ -#define BUILDCC_INCLUDE_BUILDCC_H_ - -// clang-format off - -// Core persistent environment -#include "env.h" -#include "logging.h" - -// Base -#include "toolchain.h" -#include "target.h" - -// Specialized Toolchain -#include "toolchain_gcc.h" -#include "toolchain_msvc.h" - -// TODO, Add more specialized toolchains here - -// Target - -// GCC -#include "executable_target_gcc.h" -#include "static_target_gcc.h" -#include "dynamic_target_gcc.h" - -// MSVC -#include "executable_target_msvc.h" -#include "static_target_msvc.h" -#include "dynamic_target_msvc.h" - -// TODO, Add more specialized targets here - -// BuildCC Modules -#include "args.h" -#include "register.h" - -#endif diff --git a/buildcc/include/register.h b/buildcc/include/register.h deleted file mode 100644 index d2d86187..00000000 --- a/buildcc/include/register.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef BUILDCC_INCLUDE_REGISTER_H_ -#define BUILDCC_INCLUDE_REGISTER_H_ - -#include -#include - -#include "args.h" -#include "target.h" - -#include "taskflow/taskflow.hpp" - -namespace buildcc { - -class Register { -public: - struct TestInfo { - base::Target &target_; - std::function cb_; - - TestInfo(base::Target &target, std::function cb) - : target_(target), cb_(cb) {} - }; - -public: - Register(const Args &args) : args_(args) {} - - void Env(); - void Clean(std::function clean_cb); - - void Build(const Args::Toolchain &args_toolchain, base::Target &target, - std::function build_cb); - void Test(const Args::Toolchain &args_toolchain, base::Target &target, - std::function test_cb); - - void Dep(const base::Target &target, const base::Target &dependency); - - void RunBuild(); - void RunTest(); - - // Getters - const tf::Taskflow &GetTaskflow() const { return taskflow_; } - -private: - const Args &args_; - tf::Executor executor_; - tf::Taskflow taskflow_{"Targets"}; - - std::unordered_map tests_; - std::unordered_map deps_; -}; - -} // namespace buildcc - -#endif diff --git a/buildcc/lib/args/CMakeLists.txt b/buildcc/lib/args/CMakeLists.txt new file mode 100644 index 00000000..04b82e0d --- /dev/null +++ b/buildcc/lib/args/CMakeLists.txt @@ -0,0 +1,102 @@ +# Args test +if (${TESTING}) +add_library(mock_args + src/args.cpp + mock/parse.cpp + src/register.cpp + mock/tasks.cpp +) +target_include_directories(mock_args PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/mock +) +target_compile_options(mock_args PUBLIC + ${TEST_COMPILE_FLAGS} ${BUILD_COMPILE_FLAGS} +) +target_link_options(mock_args PUBLIC + ${TEST_LINK_FLAGS} ${BUILD_LINK_FLAGS} +) +target_link_libraries(mock_args PUBLIC + CLI11::CLI11 + + mock_target + mock_toolchain_specialized + + CppUTest + CppUTestExt + ${TEST_LINK_LIBS} +) + +# Tests +add_executable(test_args + test/test_args.cpp +) +target_link_libraries(test_args PRIVATE + mock_args +) + +add_executable(test_register + test/test_register.cpp +) +target_link_libraries(test_register PRIVATE + mock_args +) + +add_test(NAME test_args COMMAND test_args + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test +) +add_test(NAME test_register COMMAND test_register + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test +) +endif() + +set(ARGS_SRCS + src/args.cpp + src/parse.cpp + src/register.cpp + src/tasks.cpp + include/args/args.h + include/args/register.h +) + +if(${BUILDCC_BUILD_AS_SINGLE_LIB}) + target_sources(buildcc PRIVATE + ${ARGS_SRCS} + ) + target_include_directories(buildcc PUBLIC + $ + $ + ) +endif() + +if(${BUILDCC_BUILD_AS_INTERFACE}) + m_clangtidy("args") + add_library(args + ${ARGS_SRCS} + ) + target_include_directories(args PUBLIC + $ + $ + ) + target_link_libraries(args PUBLIC + CLI11::CLI11 + ) + target_link_libraries(args PRIVATE + target + + toolchain_specialized + ) + target_compile_options(args PRIVATE ${BUILD_COMPILE_FLAGS}) + target_link_options(args PRIVATE ${BUILD_LINK_FLAGS}) + + # Args install + if (${BUILDCC_INSTALL}) + install(TARGETS args DESTINATION lib EXPORT argsConfig) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}") + install(EXPORT argsConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/args") + endif() +endif() + +if (${BUILDCC_INSTALL}) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}") +endif() diff --git a/buildcc/lib/args/include/args/args.h b/buildcc/lib/args/include/args/args.h new file mode 100644 index 00000000..bb32faf3 --- /dev/null +++ b/buildcc/lib/args/include/args/args.h @@ -0,0 +1,177 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ARGS_ARGS_H_ +#define ARGS_ARGS_H_ + +#include + +// Third Party +#include "CLI/CLI.hpp" + +// BuildCC +#include "env/logging.h" + +#include "toolchains/toolchain_specialized.h" + +#include "target/common/target_config.h" + +namespace fs = std::filesystem; + +namespace buildcc { + +/** + * @brief Toolchain State used to selectively build and test targets + */ +struct ArgToolchainState { + ArgToolchainState(bool b = false, bool t = false) : build(b), test(t) {} + bool build; + bool test; +}; + +/** + * @brief Toolchain Arg used to receive toolchain information through the + * command line + * Bundled with Toolchain State + */ +class ArgToolchain { +public: + ArgToolchain() + : ArgToolchain(ToolchainId::Undefined, "", ToolchainExecutables(), + ToolchainConfig()) {} + ArgToolchain(ToolchainId initial_id, const std::string &initial_name, + const ToolchainExecutables &initial_executables, + const ToolchainConfig &initial_config) + : id(initial_id), name(initial_name), executables(initial_executables), + config(initial_config) {} + + Toolchain &ConstructToolchain() { + return Toolchain_generic::New(id, name, executables, config); + } + +public: + ArgToolchainState state; + ToolchainId id; + std::string name; + ToolchainExecutables executables; + ToolchainConfig config; +}; + +// NOTE, Incomplete without pch_compile_command +// TODO, Update this for PCH +struct ArgTarget { + ArgTarget(){}; + + TargetConfig GetTargetConfig() { + TargetConfig config; + config.compile_command = compile_command; + config.link_command = link_command; + return config; + } + + std::string compile_command; + std::string link_command; +}; + +struct ArgCustom { + virtual void Add(CLI::App &app) = 0; +}; + +class Args { +private: + class Instance; + struct Internal; + +public: + Args() = delete; + Args(const Args &) = delete; + Args(Args &&) = delete; + + static Instance &Init(); + static void Deinit(); + + // Getters + static bool IsInit(); + static bool IsParsed(); + static bool Clean(); + static env::LogLevel GetLogLevel(); + + static const fs::path &GetProjectRootDir(); + static const fs::path &GetProjectBuildDir(); + +private: + static void RootArgs(); + static Internal &RefInternal(); + static CLI::App &RefApp(); + +private: + static std::unique_ptr internal_; +}; + +class Args::Instance { +public: + /** + * @brief Parse command line information to CLI11 + * + * @param argc from int main(int argc, char ** argv) + * @param argv from int main(int argc, char ** argv) + */ + static void Parse(int argc, const char *const *argv); + + /** + * @brief Add toolchain with a unique name and description + * + * @param out Receive the toolchain information through the CLI + * @param initial Set the default toolchain information as a fallback + */ + Instance &AddToolchain(const std::string &name, + const std::string &description, ArgToolchain &out, + const ArgToolchain &initial = ArgToolchain()); + + /** + * @brief Add toolchain with a unique name and description + * + * @param out Receive the toolchain information through the CLI + * @param initial Set the default toolchain information as a fallback + */ + Instance &AddTarget(const std::string &name, const std::string &description, + ArgTarget &out, const ArgTarget &initial = ArgTarget()); + + /** + * @brief Custom callback for data + * + * @param add_cb Add callback that exposes underlying CLI::App + */ + Instance &AddCustomCallback(const std::function &add_cb); + + /** + * @brief Add custom data + * + * @param data Derive from `buildcc::ArgCustom` and override the `Add` API + */ + Instance &AddCustomData(ArgCustom &data); +}; + +struct Args::Internal { + Instance instance; + CLI::App app{"BuildCC Buildsystem"}; + CLI::App *toolchain{nullptr}; + CLI::App *target{nullptr}; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/args/include/args/register.h b/buildcc/lib/args/include/args/register.h new file mode 100644 index 00000000..76bfa311 --- /dev/null +++ b/buildcc/lib/args/include/args/register.h @@ -0,0 +1,206 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ARGS_REGISTER_H_ +#define ARGS_REGISTER_H_ + +#include +#include + +#include "args.h" +#include "args/register/test_info.h" + +#include "target/custom_generator.h" +#include "target/file_generator.h" +#include "target/target.h" + +#include "taskflow/taskflow.hpp" + +namespace buildcc { + +class Reg { +private: + class Instance; + class CallbackInstance; + class ToolchainInstance; + +public: + Reg() = delete; + Reg(const Reg &) = delete; + Reg(Reg &&) = delete; + + static void Init(); + static void Deinit(); + static void Run(const std::function &post_build_cb = + std::function()); + static CallbackInstance Call(bool condition = true); + static ToolchainInstance Toolchain(const ArgToolchainState &condition); + + static const tf::Taskflow &GetTaskflow(); + +private: + static Instance &Ref(); + +private: + static std::unique_ptr instance_; +}; + +class Reg::Instance { +public: + /** + * @brief Generic register callback with variable arguments + * Can be used to organize code into functional chunks + */ + template + static void Callback(const C &build_cb, Params &&...params) { + build_cb(std::forward(params)...); + } + + /** + * @brief Generic register callback that is run when `expression == + * true` + * Can be used to add Toolchain-Target specific information + */ + template + static void CallbackIf(bool expression, const C &build_cb, + Params &&...params) { + if (expression) { + Callback(build_cb, std::forward(params)...); + } + } + + template + void Build(const C &build_cb, T &builder, Params &&...params) { + constexpr bool is_supported_base = + std::is_base_of_v; + static_assert(is_supported_base, + "Build only supports Generator, Target and derivatives"); + + build_cb(builder, std::forward(params)...); + tf::Task task = BuildTask(builder); + BuildStoreTask(builder.GetUniqueId(), task); + } + + /** + * @brief Setup dependency between 2 Targets + * PreReq: Call `Reg::Instance::Build` before calling `Reg::Instance::Dep` + * + * Target runs after dependency is built + */ + void Dep(const internal::BuilderInterface &target, + const internal::BuilderInterface &dependency); + + /** + * @brief Instance the Target to be run + * PreReq: Call `Reg::Instance::Build` before calling `Reg::Instance::Test` + * PreReq: Requires ArgToolchainState::build && ArgToolchainState::test to be + * true + * + * Target is added as the `{executable}` argument. + * We can add more fmt::format arguments using the TestConfig arguments + * parameter + */ + void Test(const std::string &command, const BaseTarget &target, + const TestConfig &config = TestConfig()); + + /** + * @brief Builds the targets that have been dynamically added through + * `Reg::Instance::Build` + */ + void RunBuild(); + + /** + * @brief Runs the targets that have been dynamically added through + * `Reg::Instance::Test` + */ + void RunTest(); + + // TODO, Add a function to create Taskflow .dot dump into file or string + + // Getters + const tf::Taskflow &GetTaskflow() const { return build_tf_; } + +private: + // BuildTasks + tf::Task BuildTask(BaseTarget &target); + tf::Task BuildTask(CustomGenerator &generator); + void BuildStoreTask(const std::string &unique_id, const tf::Task &task); + +private: + // Build + tf::Taskflow build_tf_{"Targets"}; + + std::unordered_map build_; + std::unordered_map tests_; +}; + +class Reg::CallbackInstance { +public: + CallbackInstance(bool condition = true) : condition_(condition) {} + + // Duplicated code + template + CallbackInstance &Func(const C &cb, Params &&...params) { + Instance::CallbackIf(condition_, cb, std::forward(params)...); + return *this; + } + + template + CallbackInstance &Build(const C &build_cb, T &builder, Params &&...params) { + if (condition_) { + Ref().Build(build_cb, builder, std::forward(params)...); + }; + return *this; + } + +private: + bool condition_; +}; + +class Reg::ToolchainInstance { +public: + ToolchainInstance(const ArgToolchainState &condition) + : condition_(condition) {} + + template + ToolchainInstance &Func(const C &cb, Params &&...params) { + Instance::CallbackIf(condition_.build, cb, std::forward(params)...); + return *this; + } + + template + ToolchainInstance &Build(const C &build_cb, T &builder, Params &&...params) { + if (condition_.build) { + Ref().Build(build_cb, builder, std::forward(params)...); + }; + return *this; + } + // TODO, Update/Change this + template ToolchainInstance &BuildPackage(P &package) { + return Func([&]() { package.Setup(condition_); }); + } + ToolchainInstance &Dep(const internal::BuilderInterface &target, + const internal::BuilderInterface &dependency); + ToolchainInstance &Test(const std::string &command, const BaseTarget &target, + const TestConfig &config = TestConfig()); + +private: + ArgToolchainState condition_; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/args/include/args/register/test_info.h b/buildcc/lib/args/include/args/register/test_info.h new file mode 100644 index 00000000..dffda12f --- /dev/null +++ b/buildcc/lib/args/include/args/register/test_info.h @@ -0,0 +1,118 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ARGS_REGISTER_TEST_INFO_H_ +#define ARGS_REGISTER_TEST_INFO_H_ + +#include +#include + +#include "env/optional.h" + +#include "target/target.h" + +namespace fs = std::filesystem; + +namespace buildcc { + +struct TestOutput { + enum class Type { + DefaultBehaviour, ///< Do not redirect to user or tests, default printed on + ///< console + TestPrintOnStderr, ///< Test only redirects stderr and prints + TestPrintOnStdout, ///< Test only redirects stdout and prints + TestPrintOnStderrAndStdout, ///< Test redirects both and prints + UserRedirect, ///< Redirects to user variables + }; + + /** + * @brief Configure your `Reg::Instance::Test` to get test output + * + * @param output_type Select your output type (behaviour) + * @param redirect_stdout User stdout redirection + * @param redirect_stderr User stderr redirection + */ + TestOutput(Type output_type = Type::TestPrintOnStderrAndStdout, + std::vector *redirect_stdout = nullptr, + std::vector *redirect_stderr = nullptr) + : type_(output_type), redirect_stdout_to_user_(redirect_stdout), + redirect_stderr_to_user_(redirect_stderr) {} + + Type GetType() const { return type_; } + std::vector *GetRedirectStdoutToUser() const { + return redirect_stdout_to_user_; + } + std::vector *GetRedirectStderrToUser() const { + return redirect_stderr_to_user_; + } + +private: + Type type_; + std::vector *redirect_stdout_to_user_; + std::vector *redirect_stderr_to_user_; +}; + +struct TestConfig { +public: + /** + * @brief Configure your `Reg::Instance::Test` using TestConfig + * + * @param arguments fmt::format args passed to test commands + * @param working_directory Working directory from which the test runs + * @param output Output from tests + */ + // ! FIXME, warning: base class ‘struct + // tl::detail::optional_operations_base’ + // should be explicitly initialized in the copy constructor + TestConfig( + const std::unordered_map &arguments = {}, + const env::optional &working_directory = {}, + const TestOutput &output = TestOutput()) + : arguments_(arguments), working_directory_(working_directory), + output_(output) {} + + const std::unordered_map &GetArguments() const { + return arguments_; + } + const env::optional &GetWorkingDirectory() const { + return working_directory_; + } + const TestOutput &GetTestOutput() const { return output_; } + +private: + std::unordered_map arguments_; + env::optional working_directory_; + TestOutput output_; +}; + +// PRIVATE + +struct TestInfo { + TestInfo(const BaseTarget &target, const std::string &command, + const TestConfig &config = TestConfig()) + : target_(target), command_(command), config_(config) {} + + void TestRunner() const; + +private: + const BaseTarget &target_; + std::string command_; + TestConfig config_; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/args/mock/parse.cpp b/buildcc/lib/args/mock/parse.cpp new file mode 100644 index 00000000..db3a1c6f --- /dev/null +++ b/buildcc/lib/args/mock/parse.cpp @@ -0,0 +1,15 @@ +#include "args/args.h" + +#include "env/assert_fatal.h" + +namespace buildcc { + +void Args::Instance::Parse(int argc, const char *const *argv) { + try { + RefApp().parse(argc, argv); + } catch (const CLI::ParseError &e) { + env::assert_fatal(e.what()); + } +} + +} // namespace buildcc diff --git a/buildcc/lib/args/mock/tasks.cpp b/buildcc/lib/args/mock/tasks.cpp new file mode 100644 index 00000000..b1fbb92b --- /dev/null +++ b/buildcc/lib/args/mock/tasks.cpp @@ -0,0 +1,23 @@ +#include "args/register.h" + +#include "CppUTestExt/MockSupport.h" + +namespace buildcc { + +tf::Task Reg::Instance::BuildTask(BaseTarget &target) { + mock().actualCall(fmt::format("BuildTask_{}", target.GetName()).c_str()); + return build_tf_.placeholder().name(target.GetUniqueId()); +} +tf::Task Reg::Instance::BuildTask(CustomGenerator &generator) { + mock().actualCall(fmt::format("BuildTask_{}", generator.GetName()).c_str()); + return build_tf_.placeholder().name(generator.GetUniqueId()); +} + +void Reg::Instance::RunBuild() {} + +void Reg::Instance::RunTest() { + std::for_each(tests_.begin(), tests_.end(), + [](const auto &p) { p.second.TestRunner(); }); +} + +} // namespace buildcc diff --git a/buildcc/lib/args/src/args.cpp b/buildcc/lib/args/src/args.cpp new file mode 100644 index 00000000..ce0b05c3 --- /dev/null +++ b/buildcc/lib/args/src/args.cpp @@ -0,0 +1,231 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "args/args.h" + +namespace { + +// Error messages +constexpr const char *const kArgsNotInit = + "Initialize Args using the Args::Init API"; + +// Groups +constexpr const char *const kRootGroup = "Root"; + +// Options & Flags +constexpr const char *const kHelpAllParam = "--help-all"; +constexpr const char *const kHelpAllDesc = "Expand individual options."; + +constexpr const char *const kConfigParam = "--config"; +constexpr const char *const kConfigDesc = "Read .toml files."; +constexpr int kMinFiles = 0; +constexpr int kMaxFiles = 10; + +constexpr const char *const kCleanParam = "--clean"; +constexpr const char *const kCleanDesc = "Clean artifacts"; + +constexpr const char *const kLoglevelParam = "--loglevel"; +constexpr const char *const kLoglevelDesc = "LogLevel settings"; + +constexpr const char *const kRootDirParam = "--root_dir"; +constexpr const char *const kRootDirDesc = + "Project root directory (relative to current directory)"; + +constexpr const char *const kBuildDirParam = "--build_dir"; +constexpr const char *const kBuildDirDesc = + "Project build dir (relative to current directory)"; + +// Subcommands +constexpr const char *const kToolchainSubcommand = "toolchain"; +constexpr const char *const kToolchainDesc = "Select Toolchain"; +constexpr const char *const kToolchainGroup = "Supported Toolchains"; +constexpr const char *const kToolchainIdDesc = "Toolchain ID settings"; + +constexpr const char *const kToolchainBuildParam = "--build"; +constexpr const char *const kToolchainTestParam = "--test"; +constexpr const char *const kToolchainIdParam = "--id"; +constexpr const char *const kToolchainNameParam = "--name"; +constexpr const char *const kToolchainAsmCompilerParam = "--asm_compiler"; +constexpr const char *const kToolchainCCompilerParam = "--c_compiler"; +constexpr const char *const kToolchainCppCompilerParam = "--cpp_compiler"; +constexpr const char *const kToolchainArchiverParam = "--archiver"; +constexpr const char *const kToolchainLinkerParam = "--linker"; + +constexpr const char *const kTargetSubcommand = "target"; +constexpr const char *const kTargetDesc = "Select Target"; +constexpr const char *const kTargetGroup = "Custom Targets"; + +constexpr const char *const kTargetCompileCommandParam = "--compile_command"; +constexpr const char *const kTargetLinkCommandParam = "--link_command"; + +const std::unordered_map kLogLevelMap{ + {"trace", buildcc::env::LogLevel::Trace}, + {"debug", buildcc::env::LogLevel::Debug}, + {"info", buildcc::env::LogLevel::Info}, + {"warning", buildcc::env::LogLevel::Warning}, + {"critical", buildcc::env::LogLevel::Critical}, +}; + +const std::unordered_map kToolchainIdMap{ + {"gcc", buildcc::ToolchainId::Gcc}, + {"msvc", buildcc::ToolchainId::Msvc}, + {"mingw", buildcc::ToolchainId::MinGW}, + {"clang", buildcc::ToolchainId::Clang}, + {"custom", buildcc::ToolchainId::Custom}, + {"undefined", buildcc::ToolchainId::Undefined}, +}; + +// Static variables +bool clean_{false}; +buildcc::env::LogLevel loglevel_{buildcc::env::LogLevel::Info}; +fs::path project_root_dir_{""}; +fs::path project_build_dir_{"_internal"}; + +} // namespace + +namespace buildcc { + +std::unique_ptr Args::internal_; + +Args::Instance &Args::Init() { + if (!internal_) { + internal_ = std::make_unique(); + auto &app = RefApp(); + internal_->toolchain = + app.add_subcommand(kToolchainSubcommand, kToolchainDesc); + internal_->target = app.add_subcommand(kTargetSubcommand, kTargetDesc); + RootArgs(); + } + return internal_->instance; +} + +void Args::Deinit() { internal_.reset(nullptr); } + +bool Args::IsInit() { return static_cast(internal_); } +bool Args::IsParsed() { + if (!IsInit()) { + return false; + } + return RefApp().parsed(); +} +bool Args::Clean() { return clean_; } +env::LogLevel Args::GetLogLevel() { return loglevel_; } + +const fs::path &Args::GetProjectRootDir() { return project_root_dir_; } +const fs::path &Args::GetProjectBuildDir() { return project_build_dir_; } + +// Private + +void Args::RootArgs() { + auto &app = RefApp(); + app.set_help_all_flag(kHelpAllParam, kHelpAllDesc); + + app.set_config(kConfigParam, "", kConfigDesc)->expected(kMinFiles, kMaxFiles); + + // Root flags + auto *root_group = app.add_option_group(kRootGroup); + + root_group->add_flag(kCleanParam, clean_, kCleanDesc); + root_group->add_option(kLoglevelParam, loglevel_, kLoglevelDesc) + ->transform(CLI::CheckedTransformer(kLogLevelMap, CLI::ignore_case)); + + // Dir flags + root_group->add_option(kRootDirParam, project_root_dir_, kRootDirDesc) + ->required(); + root_group->add_option(kBuildDirParam, project_build_dir_, kBuildDirDesc) + ->required(); +} + +Args::Internal &Args::RefInternal() { + env::assert_fatal(internal_ != nullptr, kArgsNotInit); + return *internal_; +} +CLI::App &Args::RefApp() { return RefInternal().app; } + +// Args::Instance + +/** + * @brief Add toolchain with a unique name and description + * + * @param out Receive the toolchain information through the CLI + * @param initial Set the default toolchain information as a fallback + */ +Args::Instance &Args::Instance::AddToolchain(const std::string &name, + const std::string &description, + ArgToolchain &out, + const ArgToolchain &initial) { + CLI::App *toolchain = RefInternal().toolchain; + CLI::App *t_user = + toolchain->add_subcommand(name, description)->group(kToolchainGroup); + + // State + t_user->add_flag(kToolchainBuildParam, out.state.build); + t_user->add_flag(kToolchainTestParam, out.state.test); + + // Id, Name, Executables + t_user->add_option(kToolchainIdParam, out.id, kToolchainIdDesc) + ->transform(CLI::CheckedTransformer(kToolchainIdMap, CLI::ignore_case)) + ->default_val(initial.id); + t_user->add_option(kToolchainNameParam, out.name)->default_val(initial.name); + t_user->add_option(kToolchainAsmCompilerParam, out.executables.assembler) + ->default_val(initial.executables.assembler); + t_user->add_option(kToolchainCCompilerParam, out.executables.c_compiler) + ->default_val(initial.executables.c_compiler); + t_user->add_option(kToolchainCppCompilerParam, out.executables.cpp_compiler) + ->default_val(initial.executables.cpp_compiler); + t_user->add_option(kToolchainArchiverParam, out.executables.archiver) + ->default_val(initial.executables.archiver); + t_user->add_option(kToolchainLinkerParam, out.executables.linker) + ->default_val(initial.executables.linker); + + // TODO, Add toolchain config + return *this; +} + +/** + * @brief Add toolchain with a unique name and description + * + * @param out Receive the toolchain information through the CLI + * @param initial Set the default toolchain information as a fallback + */ +Args::Instance &Args::Instance::AddTarget(const std::string &name, + const std::string &description, + ArgTarget &out, + const ArgTarget &initial) { + CLI::App *target = RefInternal().target; + CLI::App *targetuser = + target->add_subcommand(name, description)->group(kTargetGroup); + targetuser->add_option(kTargetCompileCommandParam, out.compile_command) + ->default_val(initial.compile_command); + targetuser->add_option(kTargetLinkCommandParam, out.link_command) + ->default_val(initial.link_command); + return *this; +} + +Args::Instance &Args::Instance::AddCustomCallback( + const std::function &add_cb) { + auto &app = RefApp(); + add_cb(app); + return *this; +} + +Args::Instance &Args::Instance::AddCustomData(ArgCustom &data) { + auto &app = RefApp(); + data.Add(app); + return *this; +} + +} // namespace buildcc diff --git a/buildcc/lib/args/src/parse.cpp b/buildcc/lib/args/src/parse.cpp new file mode 100644 index 00000000..0d2aa186 --- /dev/null +++ b/buildcc/lib/args/src/parse.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "args/args.h" + +namespace buildcc { + +void Args::Instance::Parse(int argc, const char *const *argv) { + auto &app = RefApp(); + try { + app.parse(argc, argv); + } catch (const CLI::ParseError &e) { + exit(app.exit(e)); + } +} + +} // namespace buildcc diff --git a/buildcc/lib/args/src/register.cpp b/buildcc/lib/args/src/register.cpp new file mode 100644 index 00000000..e141541c --- /dev/null +++ b/buildcc/lib/args/src/register.cpp @@ -0,0 +1,266 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "args/register.h" + +#include +#include + +#include "fmt/format.h" + +#include "env/assert_fatal.h" +#include "env/env.h" +#include "env/storage.h" + +namespace fs = std::filesystem; + +namespace { +constexpr const char *const kRegkNotInit = + "Initialize Reg using the Reg::Init API"; +} + +namespace { + +void DepDetectDuplicate(const tf::Task &target_task, const std::string &match) { + target_task.for_each_dependent([&](const tf::Task &t) { + buildcc::env::log_trace("for_each_dependent", t.name()); + buildcc::env::assert_fatal( + !(t.name() == match), + fmt::format("Dependency '{}' already added", t.name())); + }); +} + +void DepDetectCyclicDependency(const tf::Task &target_task, + const std::string &match) { + std::queue taskqueue; + taskqueue.push(target_task); + + while (!taskqueue.empty()) { + tf::Task current_task = taskqueue.front(); + taskqueue.pop(); + + current_task.for_each_successor([&](const tf::Task &t) { + buildcc::env::log_trace("for_each_successor", t.name()); + taskqueue.push(t); + buildcc::env::assert_fatal( + !(t.name() == match), + fmt::format("Cyclic dependency detected when adding '{}'", t.name())); + }); + } +} + +} // namespace + +namespace buildcc { + +std::unique_ptr Reg::instance_; + +void SystemInit() { + Project::Init(fs::current_path() / Args::GetProjectRootDir(), + fs::current_path() / Args::GetProjectBuildDir()); + env::set_log_level(Args::GetLogLevel()); + + // Top down (what is init first gets deinit last) + std::atexit([]() { + Project::Deinit(); + Reg::Deinit(); + Args::Deinit(); + Storage::Clear(); + }); +} + +void Reg::Init() { + if (!instance_) { + instance_ = std::make_unique(); + env::assert_fatal(static_cast(instance_), "Reg::Init failed"); + env::assert_fatal(Args::IsParsed(), "Setup your Args"); + + // Initialize everything else here + SystemInit(); + } +} + +void Reg::Deinit() { + instance_.reset(nullptr); + Project::Deinit(); +} + +void Reg::Run(const std::function &post_build_cb) { + auto &ref = Ref(); + ref.RunBuild(); + if (post_build_cb) { + post_build_cb(); + } + ref.RunTest(); +} + +const tf::Taskflow &Reg::GetTaskflow() { return Ref().GetTaskflow(); } + +Reg::Instance &Reg::Ref() { + env::assert_fatal(instance_ != nullptr, kRegkNotInit); + return *instance_; +} + +// Reg::ToolchainInstance + +Reg::ToolchainInstance Reg::Toolchain(const ArgToolchainState &condition) { + env::assert_fatal(instance_ != nullptr, kRegkNotInit); + return ToolchainInstance(condition); +} + +Reg::ToolchainInstance & +Reg::ToolchainInstance::Dep(const internal::BuilderInterface &target, + const internal::BuilderInterface &dependency) { + if (condition_.build) { + Ref().Dep(target, dependency); + } + return *this; +} + +Reg::ToolchainInstance &Reg::ToolchainInstance::Test(const std::string &command, + const BaseTarget &target, + const TestConfig &config) { + if (condition_.build && condition_.test) { + Ref().Test(command, target, config); + } + return *this; +} + +// Reg::CallbackInstance + +Reg::CallbackInstance Reg::Call(bool condition) { + env::assert_fatal(instance_ != nullptr, kRegkNotInit); + return CallbackInstance(condition); +} + +// Reg::Instance + +void Reg::Instance::Dep(const internal::BuilderInterface &target, + const internal::BuilderInterface &dependency) { + const auto target_iter = build_.find(target.GetUniqueId()); + const auto dep_iter = build_.find(dependency.GetUniqueId()); + env::assert_fatal(!(target_iter == build_.end() || dep_iter == build_.end()), + "Call Instance::Build API on target and " + "dependency before Instance::Dep API"); + + const std::string &dep_unique_id = dependency.GetUniqueId(); + DepDetectDuplicate(target_iter->second, dep_unique_id); + DepDetectCyclicDependency(target_iter->second, dep_unique_id); + + // Finally do this + target_iter->second.succeed(dep_iter->second); +} + +void Reg::Instance::Test(const std::string &command, const BaseTarget &target, + const TestConfig &config) { + const auto target_iter = build_.find(target.GetUniqueId()); + env::assert_fatal( + !(target_iter == build_.end()), + "Call Instance::Build API on target before Instance::Test API"); + + const bool added = + tests_.emplace(target.GetUniqueId(), TestInfo(target, command, config)) + .second; + env::assert_fatal( + added, fmt::format("Could not register test {}", target.GetName())); +} + +// Private + +void Reg::Instance::BuildStoreTask(const std::string &unique_id, + const tf::Task &task) { + const bool stored = build_.emplace(unique_id, task).second; + env::assert_fatal( + stored, fmt::format("Duplicate `Instance::Build` call detected for '{}'", + unique_id)); +} + +// + +void TestInfo::TestRunner() const { + env::log_info(__FUNCTION__, + fmt::format("Testing \'{}\'", target_.GetUniqueId())); + env::Command command; + command.AddDefaultArguments({ + {"executable", fmt::format("{}", target_.GetTargetPath())}, + }); + const std::string test_command = + command.Construct(command_, config_.GetArguments()); + + // TODO, Shift this to a function + std::vector test_redirect_stdout; + std::vector test_redirect_stderr; + + std::vector *redirect_stdout{nullptr}; + std::vector *redirect_stderr{nullptr}; + switch (config_.GetTestOutput().GetType()) { + case TestOutput::Type::DefaultBehaviour: + (void)test_redirect_stdout; + (void)test_redirect_stderr; + break; + case TestOutput::Type::TestPrintOnStderr: + redirect_stderr = &test_redirect_stderr; + (void)test_redirect_stdout; + break; + case TestOutput::Type::TestPrintOnStdout: + redirect_stdout = &test_redirect_stdout; + (void)test_redirect_stderr; + break; + case TestOutput::Type::TestPrintOnStderrAndStdout: + redirect_stdout = &test_redirect_stdout; + redirect_stderr = &test_redirect_stderr; + break; + case TestOutput::Type::UserRedirect: + redirect_stdout = config_.GetTestOutput().GetRedirectStdoutToUser(); + redirect_stderr = config_.GetTestOutput().GetRedirectStderrToUser(); + (void)test_redirect_stdout; + (void)test_redirect_stderr; + break; + default: + (void)test_redirect_stdout; + (void)test_redirect_stderr; + env::assert_fatal("Invalid TestOutput::Type"); + break; + }; + + const bool success = + env::Command::Execute(test_command, config_.GetWorkingDirectory(), + redirect_stdout, redirect_stderr); + env::assert_fatal(success, + fmt::format("Could not run {}", target_.GetUniqueId())); + + // Print + switch (config_.GetTestOutput().GetType()) { + case TestOutput::Type::TestPrintOnStderr: + env::log_info(fmt::format("STDERR: {}", target_.GetUniqueId()), + internal::aggregate(*redirect_stderr)); + break; + case TestOutput::Type::TestPrintOnStdout: + env::log_info(fmt::format("STDOUT: {}", target_.GetUniqueId()), + internal::aggregate(*redirect_stdout)); + break; + case TestOutput::Type::TestPrintOnStderrAndStdout: + env::log_info(fmt::format("STDOUT: {}", target_.GetUniqueId()), + internal::aggregate(*redirect_stdout)); + env::log_info(fmt::format("STDERR: {}", target_.GetUniqueId()), + internal::aggregate(*redirect_stderr)); + break; + default: + break; + } +} + +} // namespace buildcc diff --git a/buildcc/lib/args/src/tasks.cpp b/buildcc/lib/args/src/tasks.cpp new file mode 100644 index 00000000..8328671d --- /dev/null +++ b/buildcc/lib/args/src/tasks.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "args/register.h" + +#include "env/logging.h" +#include "env/util.h" + +namespace buildcc { + +tf::Task Reg::Instance::BuildTask(BaseTarget &target) { + return build_tf_.composed_of(target.GetTaskflow()).name(target.GetUniqueId()); +} + +tf::Task Reg::Instance::BuildTask(CustomGenerator &generator) { + return build_tf_.composed_of(generator.GetTaskflow()) + .name(generator.GetUniqueId()); +} + +void Reg::Instance::RunBuild() { + tf::Executor executor; + env::log_info(__FUNCTION__, + fmt::format("Running with {} workers", executor.num_workers())); + executor.run(build_tf_); + executor.wait_for_all(); + env::assert_fatal(env::get_task_state() == env::TaskState::SUCCESS, + "Task state is not successful!"); +} + +void Reg::Instance::RunTest() { + tf::Taskflow test_tf{"Tests"}; + test_tf.for_each( + tests_.begin(), tests_.end(), + [](const std::pair &p) { p.second.TestRunner(); }); + + tf::Executor executor; + executor.run(test_tf); + executor.wait_for_all(); +} + +} // namespace buildcc diff --git a/buildcc/lib/args/test/configs/basic_parse.toml b/buildcc/lib/args/test/configs/basic_parse.toml new file mode 100644 index 00000000..ce6b2deb --- /dev/null +++ b/buildcc/lib/args/test/configs/basic_parse.toml @@ -0,0 +1,7 @@ +# Root +root_dir = "root" +build_dir = "build" +loglevel = "trace" + +# Project +clean = true diff --git a/buildcc/lib/args/test/configs/gcc_target.toml b/buildcc/lib/args/test/configs/gcc_target.toml new file mode 100644 index 00000000..1f3d6596 --- /dev/null +++ b/buildcc/lib/args/test/configs/gcc_target.toml @@ -0,0 +1,3 @@ +[target.gcc] +compile_command = "{compiler} {preprocessor_flags} {include_dirs} {common_compile_flags} {compile_flags} -o {output} -c {input}" +link_command = "{cpp_compiler} {link_flags} {compiled_sources} -o {output} {lib_dirs} {lib_deps}" diff --git a/buildcc/lib/args/test/configs/gcc_toolchain.toml b/buildcc/lib/args/test/configs/gcc_toolchain.toml new file mode 100644 index 00000000..7edd1ec9 --- /dev/null +++ b/buildcc/lib/args/test/configs/gcc_toolchain.toml @@ -0,0 +1,11 @@ +# Depending on the compile_command and link_command associated with the relevant target these are the minimal parameters required +[toolchain.gcc] +build = true +test = false +id = "gcc" +name = "gcc" +asm_compiler = "as" +c_compiler = "gcc" +cpp_compiler = "g++" +archiver = "ar" +linker = "ld" diff --git a/buildcc/lib/args/test/configs/msvc_target.toml b/buildcc/lib/args/test/configs/msvc_target.toml new file mode 100644 index 00000000..18e9d752 --- /dev/null +++ b/buildcc/lib/args/test/configs/msvc_target.toml @@ -0,0 +1,4 @@ +[target.msvc] +compile_command = "{compiler} {preprocessor_flags} {include_dirs} {common_compile_flags} {compile_flags} /Fo{output} /c {input}" +link_command = "{linker} {link_flags} {lib_dirs} /OUT:{output} {lib_deps} {compiled_sources}" + diff --git a/buildcc/lib/args/test/configs/msvc_toolchain.toml b/buildcc/lib/args/test/configs/msvc_toolchain.toml new file mode 100644 index 00000000..4e6c8106 --- /dev/null +++ b/buildcc/lib/args/test/configs/msvc_toolchain.toml @@ -0,0 +1,11 @@ +# Depending on the compile_command and link_command associated with the relevant target these are the minimal parameters required +[toolchain.msvc] +build = true +test = true +id = "msvc" +name = "msvc" +asm_compiler = "cl" +c_compiler = "cl" +cpp_compiler = "cl" +archiver = "lib" +linker = "link" diff --git a/buildcc/lib/args/test/configs/no_clean.toml b/buildcc/lib/args/test/configs/no_clean.toml new file mode 100644 index 00000000..8f2cacd1 --- /dev/null +++ b/buildcc/lib/args/test/configs/no_clean.toml @@ -0,0 +1,2 @@ +# Project +clean = false diff --git a/buildcc/lib/args/test/test_args.cpp b/buildcc/lib/args/test/test_args.cpp new file mode 100644 index 00000000..8b519174 --- /dev/null +++ b/buildcc/lib/args/test/test_args.cpp @@ -0,0 +1,333 @@ +#include "args/args.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" + +// clang-format off +TEST_GROUP(ArgsTestGroup) +{ + void teardown() { + buildcc::Args::Deinit(); + } +}; +// clang-format on + +TEST(ArgsTestGroup, Args_BasicParse) { + std::vector av{"", "--config", "configs/basic_parse.toml"}; + int argc = av.size(); + + CHECK_FALSE(buildcc::Args::IsInit()); + CHECK_FALSE(buildcc::Args::IsParsed()); + + (void)buildcc::Args::Init(); + auto &instance = buildcc::Args::Init(); // Second init does nothing when + // already initialized + instance.Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + CHECK_TRUE(buildcc::Args::IsInit()); + CHECK_TRUE(buildcc::Args::IsParsed()); +} + +TEST(ArgsTestGroup, Args_BasicExit) { + UT_PRINT("Args_BasicExit\r\n"); + std::vector av{"", "--config", "configs/basic_parse.toml", + "--help"}; + int argc = av.size(); + + auto &instance = buildcc::Args::Init(); + CHECK_THROWS(std::exception, instance.Parse(argc, av.data())); +} + +TEST(ArgsTestGroup, Args_MultiToml) { + std::vector av{"", "--config", "configs/basic_parse.toml", + "--config", "configs/no_clean.toml"}; + int argc = av.size(); + + buildcc::Args::Init().Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_FALSE(buildcc::Args::Clean()); +} + +TEST(ArgsTestGroup, Args_CustomToolchain) { + std::vector av{"", "--config", "configs/basic_parse.toml", + "--config", "configs/gcc_toolchain.toml"}; + int argc = av.size(); + + buildcc::ArgToolchain gcc_toolchain; + buildcc::Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", gcc_toolchain) + .Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + // Toolchain + CHECK_TRUE(gcc_toolchain.state.build); + CHECK_FALSE(gcc_toolchain.state.test); + CHECK(gcc_toolchain.id == buildcc::ToolchainId::Gcc); + STRCMP_EQUAL(gcc_toolchain.name.c_str(), "gcc"); + STRCMP_EQUAL(gcc_toolchain.executables.assembler.c_str(), "as"); + STRCMP_EQUAL(gcc_toolchain.executables.c_compiler.c_str(), "gcc"); + STRCMP_EQUAL(gcc_toolchain.executables.cpp_compiler.c_str(), "g++"); + STRCMP_EQUAL(gcc_toolchain.executables.archiver.c_str(), "ar"); + STRCMP_EQUAL(gcc_toolchain.executables.linker.c_str(), "ld"); +} + +TEST(ArgsTestGroup, Args_MultipleCustomToolchain) { + std::vector av{ + "", + "--config", + "configs/basic_parse.toml", + "--config", + "configs/gcc_toolchain.toml", + "--config", + "configs/msvc_toolchain.toml", + }; + int argc = av.size(); + + buildcc::ArgToolchain gcc_toolchain; + buildcc::ArgToolchain msvc_toolchain; + buildcc::Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", gcc_toolchain) + .AddToolchain("msvc", "Generic msvc toolchain", msvc_toolchain) + .Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + // Toolchain + + // GCC + CHECK_TRUE(gcc_toolchain.state.build); + CHECK_FALSE(gcc_toolchain.state.test); + CHECK(gcc_toolchain.id == buildcc::ToolchainId::Gcc); + STRCMP_EQUAL(gcc_toolchain.name.c_str(), "gcc"); + STRCMP_EQUAL(gcc_toolchain.executables.assembler.c_str(), "as"); + STRCMP_EQUAL(gcc_toolchain.executables.c_compiler.c_str(), "gcc"); + STRCMP_EQUAL(gcc_toolchain.executables.cpp_compiler.c_str(), "g++"); + STRCMP_EQUAL(gcc_toolchain.executables.archiver.c_str(), "ar"); + STRCMP_EQUAL(gcc_toolchain.executables.linker.c_str(), "ld"); + + // MSVC + CHECK_TRUE(msvc_toolchain.state.build); + CHECK_TRUE(msvc_toolchain.state.test); + CHECK(msvc_toolchain.id == buildcc::ToolchainId::Msvc); + STRCMP_EQUAL(msvc_toolchain.name.c_str(), "msvc"); + STRCMP_EQUAL(msvc_toolchain.executables.assembler.c_str(), "cl"); + STRCMP_EQUAL(msvc_toolchain.executables.c_compiler.c_str(), "cl"); + STRCMP_EQUAL(msvc_toolchain.executables.cpp_compiler.c_str(), "cl"); + STRCMP_EQUAL(msvc_toolchain.executables.archiver.c_str(), "lib"); + STRCMP_EQUAL(msvc_toolchain.executables.linker.c_str(), "link"); +} + +TEST(ArgsTestGroup, Args_DuplicateCustomToolchain) { + buildcc::ArgToolchain gcc_toolchain; + buildcc::ArgToolchain other_gcc_toolchain; + auto &instance = buildcc::Args::Init().AddToolchain( + "gcc", "Generic gcc toolchain", gcc_toolchain); + + // CLI11 Throws an exception when multiple toolchains with same name are added + // NOTE, This behaviour does not need to be tested since it is provided by + // CLI11 + // This test is as an example of wrong usage by the user + CHECK_THROWS(std::exception, + (instance.AddToolchain("gcc", "Other gcc toolchain ", + other_gcc_toolchain))); +} + +TEST(ArgsTestGroup, Args_CustomTarget) { + std::vector av{ + "", + "--config", + "configs/basic_parse.toml", + "--config", + "configs/gcc_toolchain.toml", + "--config", + "configs/gcc_target.toml", + }; + int argc = av.size(); + + buildcc::ArgToolchain gcc_toolchain; + buildcc::ArgTarget gcc_target; + buildcc::Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", gcc_toolchain) + .AddTarget("gcc", "Generic gcc target", gcc_target) + .Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + // Toolchain + CHECK_TRUE(gcc_toolchain.state.build); + CHECK_FALSE(gcc_toolchain.state.test); + CHECK(gcc_toolchain.id == buildcc::ToolchainId::Gcc); + STRCMP_EQUAL(gcc_toolchain.name.c_str(), "gcc"); + STRCMP_EQUAL(gcc_toolchain.executables.assembler.c_str(), "as"); + STRCMP_EQUAL(gcc_toolchain.executables.c_compiler.c_str(), "gcc"); + STRCMP_EQUAL(gcc_toolchain.executables.cpp_compiler.c_str(), "g++"); + STRCMP_EQUAL(gcc_toolchain.executables.archiver.c_str(), "ar"); + STRCMP_EQUAL(gcc_toolchain.executables.linker.c_str(), "ld"); + + // Target + STRCMP_EQUAL(gcc_target.compile_command.c_str(), + "{compiler} {preprocessor_flags} {include_dirs} " + "{common_compile_flags} {compile_flags} -o {output} -c {input}"); + STRCMP_EQUAL(gcc_target.link_command.c_str(), + "{cpp_compiler} {link_flags} {compiled_sources} -o {output} " + "{lib_dirs} {lib_deps}"); +} + +TEST(ArgsTestGroup, Args_MultipleCustomTarget) { + std::vector av{ + "", + "--config", + "configs/basic_parse.toml", + "--config", + "configs/gcc_toolchain.toml", + "--config", + "configs/gcc_target.toml", + "--config", + "configs/msvc_toolchain.toml", + "--config", + "configs/msvc_target.toml", + }; + int argc = av.size(); + + buildcc::ArgToolchain gcc_toolchain; + buildcc::ArgTarget gcc_target; + buildcc::ArgToolchain msvc_toolchain; + buildcc::ArgTarget msvc_target; + + buildcc::Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", gcc_toolchain) + .AddTarget("gcc", "Generic gcc target", gcc_target) + .AddToolchain("msvc", "Generic msvc toolchain", msvc_toolchain) + .AddTarget("msvc", "Generic msvc target", msvc_target) + .Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + // GCC + + // Toolchain + CHECK_TRUE(gcc_toolchain.state.build); + CHECK_FALSE(gcc_toolchain.state.test); + CHECK(gcc_toolchain.id == buildcc::ToolchainId::Gcc); + STRCMP_EQUAL(gcc_toolchain.name.c_str(), "gcc"); + STRCMP_EQUAL(gcc_toolchain.executables.assembler.c_str(), "as"); + STRCMP_EQUAL(gcc_toolchain.executables.c_compiler.c_str(), "gcc"); + STRCMP_EQUAL(gcc_toolchain.executables.cpp_compiler.c_str(), "g++"); + STRCMP_EQUAL(gcc_toolchain.executables.archiver.c_str(), "ar"); + STRCMP_EQUAL(gcc_toolchain.executables.linker.c_str(), "ld"); + + // Target + STRCMP_EQUAL(gcc_target.compile_command.c_str(), + "{compiler} {preprocessor_flags} {include_dirs} " + "{common_compile_flags} {compile_flags} -o {output} -c {input}"); + STRCMP_EQUAL(gcc_target.link_command.c_str(), + "{cpp_compiler} {link_flags} {compiled_sources} -o {output} " + "{lib_dirs} {lib_deps}"); + + // MSVC + + // Toolchain + CHECK_TRUE(msvc_toolchain.state.build); + CHECK_TRUE(msvc_toolchain.state.test); + CHECK(msvc_toolchain.id == buildcc::ToolchainId::Msvc); + STRCMP_EQUAL(msvc_toolchain.name.c_str(), "msvc"); + STRCMP_EQUAL(msvc_toolchain.executables.assembler.c_str(), "cl"); + STRCMP_EQUAL(msvc_toolchain.executables.c_compiler.c_str(), "cl"); + STRCMP_EQUAL(msvc_toolchain.executables.cpp_compiler.c_str(), "cl"); + STRCMP_EQUAL(msvc_toolchain.executables.archiver.c_str(), "lib"); + STRCMP_EQUAL(msvc_toolchain.executables.linker.c_str(), "link"); + + // Target + STRCMP_EQUAL(msvc_target.compile_command.c_str(), + "{compiler} {preprocessor_flags} {include_dirs} " + "{common_compile_flags} {compile_flags} /Fo{output} /c {input}"); + STRCMP_EQUAL(msvc_target.link_command.c_str(), + "{linker} {link_flags} {lib_dirs} /OUT:{output} {lib_deps} " + "{compiled_sources}"); +} + +TEST(ArgsTestGroup, Args_CustomCallback) { + std::vector av{"", + "--config", + "configs/basic_parse.toml", + "--random_bool", + "true", + "--random_string", + "hello world"}; + int argc = av.size(); + + bool random_bool{false}; + std::string random_string; + auto &instance = buildcc::Args::Init(); + instance.AddCustomCallback([&](CLI::App &app) { + app.add_option("--random_bool", random_bool, "Random bool"); + app.add_option("--random_string", random_string, "Random string"); + }); + instance.Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + CHECK_TRUE(random_bool); + STRCMP_EQUAL(random_string.c_str(), "hello world"); +} + +TEST(ArgsTestGroup, Args_CustomData) { + struct RandomGroupedData : public buildcc::ArgCustom { + void Add(CLI::App &app) override { + app.add_option("--random_bool", random_bool, "Random bool"); + app.add_option("--random_string", random_string, "Random string"); + } + + bool random_bool{false}; + std::string random_string; + }; + + std::vector av{"", + "--config", + "configs/basic_parse.toml", + "--random_bool", + "true", + "--random_string", + "hello world"}; + int argc = av.size(); + + RandomGroupedData grouped_data; + buildcc::Args::Init().AddCustomData(grouped_data).Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + CHECK_TRUE(grouped_data.random_bool); + STRCMP_EQUAL(grouped_data.random_string.c_str(), "hello world"); +} + +int main(int ac, char **av) { + MemoryLeakWarningPlugin::destroyGlobalDetector(); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/args/test/test_register.cpp b/buildcc/lib/args/test/test_register.cpp new file mode 100644 index 00000000..04d8661e --- /dev/null +++ b/buildcc/lib/args/test/test_register.cpp @@ -0,0 +1,770 @@ +#include "args/register.h" + +#include "expect_command.h" + +#include "mock_command_copier.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(RegisterTestGroup) +{ + void teardown() { + buildcc::Reg::Deinit(); + buildcc::Args::Deinit(); + mock().clear(); + } +}; +// clang-format on + +TEST(RegisterTestGroup, Register_Initialize) { + std::vector av{"", "--config", "configs/basic_parse.toml"}; + int argc = av.size(); + + buildcc::Args::Init().Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + buildcc::Reg::Init(); + buildcc::Reg::Init(); // Second init does nothing +} + +TEST(RegisterTestGroup, Register_Clean) { + { + std::vector av{"", "--config", "configs/basic_parse.toml"}; + int argc = av.size(); + + buildcc::Args::Init().Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + buildcc::Reg::Init(); + mock().expectOneCall("CleanCb"); + buildcc::Reg::Call(buildcc::Args::Clean()).Func([]() { + mock().actualCall("CleanCb"); + }); + buildcc::Reg::Deinit(); + buildcc::Args::Deinit(); + } + + { + std::vector av{ + "", + "--config", + "configs/basic_parse.toml", + "--config", + "configs/no_clean.toml", + }; + int argc = av.size(); + + buildcc::Args::Init().Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_FALSE(buildcc::Args::Clean()); + + buildcc::Reg::Init(); + buildcc::Reg::Call(buildcc::Args::Clean()).Func([]() { + mock().actualCall("CleanCb"); + }); + buildcc::Reg::Deinit(); + buildcc::Args::Deinit(); + } + + mock().checkExpectations(); +} + +TEST(RegisterTestGroup, Register_Build) { + std::vector av{ + "", + "--config", + "configs/basic_parse.toml", + }; + int argc = av.size(); + + buildcc::ArgToolchain gcc_toolchain; + buildcc::ArgToolchain msvc_toolchain; + buildcc::Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", gcc_toolchain) + .AddToolchain("msvc", "Generic msvc toolchain", msvc_toolchain) + .Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + // Make dummy toolchain and target + buildcc::Project::Init(fs::current_path(), fs::current_path()); + buildcc::Toolchain toolchain( + buildcc::ToolchainId::Gcc, "", + buildcc::ToolchainExecutables("", "", "", "", "")); + buildcc::BaseTarget target("dummyT", buildcc::TargetType::Executable, + toolchain, ""); + + { + buildcc::ArgToolchainState state{false, false}; + + buildcc::Reg::Init(); + buildcc::Reg::Toolchain(state).Build( + [](buildcc::BaseTarget &target) { (void)target; }, target); + (void)buildcc::Reg::GetTaskflow(); + buildcc::Reg::Deinit(); + CHECK_THROWS(std::exception, buildcc::Reg::GetTaskflow()); + } + + { + buildcc::ArgToolchainState state{true, true}; + + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + buildcc::Reg::Toolchain(state).Build( + [](buildcc::BaseTarget &target) { (void)target; }, target); + buildcc::Reg::Deinit(); + } + + buildcc::Project::Deinit(); + mock().checkExpectations(); +} + +TEST(RegisterTestGroup, Register_Run_PostCb) { + std::vector av{ + "", + "--config", + "configs/basic_parse.toml", + }; + int argc = av.size(); + + buildcc::ArgToolchain gcc_toolchain; + buildcc::ArgToolchain msvc_toolchain; + buildcc::Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", gcc_toolchain) + .AddToolchain("msvc", "Generic msvc toolchain", msvc_toolchain) + .Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + // Make dummy toolchain and target + buildcc::Project::Init(fs::current_path(), fs::current_path()); + buildcc::Toolchain toolchain( + buildcc::ToolchainId::Gcc, "", + buildcc::ToolchainExecutables("", "", "", "", "")); + buildcc::BaseTarget target("dummyT", buildcc::TargetType::Executable, + toolchain, ""); + + { + buildcc::ArgToolchainState state{false, false}; + + buildcc::Reg::Init(); + buildcc::Reg::Toolchain(state).Build( + [](buildcc::BaseTarget &target) { (void)target; }, target); + + mock().expectOneCall("Build_PostCb"); + buildcc::Reg::Run([]() { mock().actualCall("Build_PostCb"); }); + buildcc::Reg::Deinit(); + } + + buildcc::Project::Deinit(); + mock().checkExpectations(); +} + +TEST(RegisterTestGroup, Register_NoBuildAndDep) { + std::vector av{ + "", + "--config", + "configs/basic_parse.toml", + }; + int argc = av.size(); + + buildcc::ArgToolchain gcc_toolchain; + buildcc::ArgToolchain msvc_toolchain; + buildcc::Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", gcc_toolchain) + .AddToolchain("msvc", "Generic msvc toolchain", msvc_toolchain) + .Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + // Make dummy toolchain and target + buildcc::Project::Init(fs::current_path(), fs::current_path()); + buildcc::Toolchain toolchain( + buildcc::ToolchainId::Gcc, "", + buildcc::ToolchainExecutables("", "", "", "", "")); + buildcc::BaseTarget target("dummyT", buildcc::TargetType::Executable, + toolchain, ""); + buildcc::BaseTarget dependency("depT", buildcc::TargetType::Executable, + toolchain, ""); + + // 4 options + // T -> Target + // D -> Dep + // T0D0 -> Throw + // T0D1 -> Throw + // T1D0 -> Throw + // T1D1 -> This is the only condition for success + // buildcc::ArgToolchainState falseState{false, false}; + buildcc::ArgToolchainState trueState{true, true}; + + // T0D0 + { + buildcc::Reg::Init(); + CHECK_THROWS(std::exception, + buildcc::Reg::Toolchain(trueState).Dep(target, dependency)); + buildcc::Reg::Deinit(); + } + + // T0D1 + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_depT"); + buildcc::Reg::Toolchain(trueState).Build( + [](buildcc::BaseTarget &target) { (void)target; }, dependency); + + CHECK_THROWS(std::exception, + buildcc::Reg::Toolchain(trueState).Dep(target, dependency)); + buildcc::Reg::Deinit(); + } + + // T1D0 + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + buildcc::Reg::Toolchain(trueState).Build( + [](buildcc::BaseTarget &target) { (void)target; }, target); + + CHECK_THROWS(std::exception, + buildcc::Reg::Toolchain(trueState).Dep(target, dependency)); + buildcc::Reg::Deinit(); + } + + // T1D1 + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + mock().expectNCalls(1, "BuildTask_depT"); + buildcc::Reg::Toolchain(trueState).Build( + [](buildcc::BaseTarget &target) { (void)target; }, target); + buildcc::Reg::Toolchain(trueState).Build( + [](buildcc::BaseTarget &target) { (void)target; }, dependency); + + buildcc::Reg::Toolchain(trueState).Dep(target, dependency); + buildcc::Reg::Deinit(); + } + + buildcc::Project::Deinit(); + mock().checkExpectations(); +} + +TEST(RegisterTestGroup, Register_BuildAndDep) { + std::vector av{ + "", + "--config", + "configs/basic_parse.toml", + }; + int argc = av.size(); + + buildcc::ArgToolchain gcc_toolchain; + buildcc::ArgToolchain msvc_toolchain; + buildcc::Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", gcc_toolchain) + .AddToolchain("msvc", "Generic msvc toolchain", msvc_toolchain) + .Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + // Make dummy toolchain and target + buildcc::Project::Init(fs::current_path(), fs::current_path()); + buildcc::Toolchain toolchain( + buildcc::ToolchainId::Gcc, "", + buildcc::ToolchainExecutables("", "", "", "", "")); + buildcc::BaseTarget target("dummyT", buildcc::TargetType::Executable, + toolchain, ""); + buildcc::BaseTarget dependency("depT", buildcc::TargetType::Executable, + toolchain, ""); + + // 4 options + // T -> Target + // D -> Dep + // T0D0 -> Ignore + // T0D1 -> Ignore + // T1D0 -> Ignore + // T1D1 -> This is the only condition for success + buildcc::ArgToolchainState falseState{false, false}; + buildcc::ArgToolchainState trueState{true, true}; + + // T0D0 + { + buildcc::Reg::Init(); + buildcc::Reg::Toolchain(falseState) + .Build([](buildcc::BaseTarget &target) { (void)target; }, target) + .Build([](buildcc::BaseTarget &target) { (void)target; }, dependency) + .Dep(target, dependency); + buildcc::Reg::Deinit(); + } + + // T0D1 + { + buildcc::Reg::Init(); + buildcc::Reg::Toolchain(falseState) + .Build([](buildcc::BaseTarget &target) { (void)target; }, target); + mock().expectNCalls(1, "BuildTask_depT"); + // In this case, target is not built so Dep throws + // Bad usage + CHECK_THROWS(std::exception, + buildcc::Reg::Toolchain(trueState) + .Build([](buildcc::BaseTarget &target) { (void)target; }, + dependency) + .Dep(target, dependency)); + buildcc::Reg::Deinit(); + } + + // T1D0 + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + buildcc::Reg::Toolchain(trueState).Build( + [](buildcc::BaseTarget &target) { (void)target; }, target); + buildcc::Reg::Toolchain(falseState) + .Build([](buildcc::BaseTarget &target) { (void)target; }, dependency); + + // In this case dependency is not built + // Bad usage + CHECK_THROWS(std::exception, + buildcc::Reg::Toolchain(trueState).Dep(target, dependency)); + buildcc::Reg::Deinit(); + } + + // T1D1 + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + mock().expectNCalls(1, "BuildTask_depT"); + buildcc::Reg::Toolchain(trueState) + .Build([](buildcc::BaseTarget &target) { (void)target; }, target) + .Build([](buildcc::BaseTarget &target) { (void)target; }, dependency) + .Dep(target, dependency); + buildcc::Reg::Deinit(); + } + + buildcc::Project::Deinit(); + mock().checkExpectations(); +} + +TEST(RegisterTestGroup, Register_DepDuplicate) { + std::vector av{ + "", + "--config", + "configs/basic_parse.toml", + }; + int argc = av.size(); + + buildcc::ArgToolchain gcc_toolchain; + buildcc::ArgToolchain msvc_toolchain; + buildcc::Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", gcc_toolchain) + .AddToolchain("msvc", "Generic msvc toolchain", msvc_toolchain) + .Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + // Make dummy toolchain and target + buildcc::Project::Init(fs::current_path(), fs::current_path()); + buildcc::Toolchain toolchain( + buildcc::ToolchainId::Gcc, "", + buildcc::ToolchainExecutables("", "", "", "", "")); + buildcc::BaseTarget target("dummyT", buildcc::TargetType::Executable, + toolchain, ""); + buildcc::BaseTarget dependency("depT", buildcc::TargetType::Executable, + toolchain, ""); + buildcc::BaseTarget dependency2("dep2T", buildcc::TargetType::Executable, + toolchain, ""); + + buildcc::ArgToolchainState trueState{true, true}; + + // Duplicate dependency with 2 Targets + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + mock().expectNCalls(1, "BuildTask_depT"); + buildcc::Reg::Toolchain(trueState) + .Build([](buildcc::BaseTarget &target) { (void)target; }, target) + .Build([](buildcc::BaseTarget &target) { (void)target; }, dependency) + .Dep(target, dependency); + CHECK_THROWS(std::exception, + buildcc::Reg::Toolchain(trueState).Dep(target, dependency)); + buildcc::Reg::Deinit(); + } + + // Duplicate dependency with 3 Targets + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + mock().expectNCalls(1, "BuildTask_depT"); + mock().expectNCalls(1, "BuildTask_dep2T"); + + buildcc::Reg::Toolchain(trueState).Build( + [](buildcc::BaseTarget &target) { (void)target; }, target); + buildcc::Reg::Toolchain(trueState).Build( + [](buildcc::BaseTarget &target) { (void)target; }, dependency); + buildcc::Reg::Toolchain(trueState).Build( + [](buildcc::BaseTarget &target) { (void)target; }, dependency2); + + buildcc::Reg::Toolchain(trueState).Dep(dependency, dependency2); + buildcc::Reg::Toolchain(trueState).Dep(target, dependency); + buildcc::Reg::Toolchain(trueState).Dep(target, dependency2); + + CHECK_THROWS(std::exception, + buildcc::Reg::Toolchain(trueState).Dep(target, dependency)); + CHECK_THROWS(std::exception, + buildcc::Reg::Toolchain(trueState).Dep(target, dependency2)); + buildcc::Reg::Deinit(); + } + + buildcc::Project::Deinit(); + mock().checkExpectations(); +} + +TEST(RegisterTestGroup, Register_DepCyclic) { + std::vector av{ + "", + "--config", + "configs/basic_parse.toml", + }; + int argc = av.size(); + + buildcc::ArgToolchain gcc_toolchain; + buildcc::ArgToolchain msvc_toolchain; + buildcc::Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", gcc_toolchain) + .AddToolchain("msvc", "Generic msvc toolchain", msvc_toolchain) + .Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + // Make dummy toolchain and target + buildcc::Project::Init(fs::current_path(), fs::current_path()); + buildcc::Toolchain toolchain( + buildcc::ToolchainId::Gcc, "", + buildcc::ToolchainExecutables("", "", "", "", "")); + buildcc::BaseTarget target("dummyT", buildcc::TargetType::Executable, + toolchain, ""); + buildcc::BaseTarget dependency("depT", buildcc::TargetType::Executable, + toolchain, ""); + buildcc::BaseTarget dependency2("dep2T", buildcc::TargetType::Executable, + toolchain, ""); + + buildcc::ArgToolchainState trueState{true, true}; + + // Immediate cyclic depdendency + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + mock().expectNCalls(1, "BuildTask_depT"); + buildcc::Reg::Toolchain(trueState).Build( + [](buildcc::BaseTarget &target) { (void)target; }, target); + buildcc::Reg::Toolchain(trueState).Build( + [](buildcc::BaseTarget &target) { (void)target; }, dependency); + + buildcc::Reg::Toolchain(trueState).Dep(target, dependency); + CHECK_THROWS(std::exception, + buildcc::Reg::Toolchain(trueState).Dep(dependency, target)); + buildcc::Reg::Deinit(); + } + + // Duplicate dependency with 3 Targets + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + mock().expectNCalls(1, "BuildTask_depT"); + mock().expectNCalls(1, "BuildTask_dep2T"); + + buildcc::Reg::Toolchain(trueState).Build( + [](buildcc::BaseTarget &target) { (void)target; }, target); + buildcc::Reg::Toolchain(trueState).Build( + [](buildcc::BaseTarget &target) { (void)target; }, dependency); + buildcc::Reg::Toolchain(trueState).Build( + [](buildcc::BaseTarget &target) { (void)target; }, dependency2); + + buildcc::Reg::Toolchain(trueState).Dep(dependency, dependency2); + buildcc::Reg::Toolchain(trueState).Dep(target, dependency); + + // dependency2 -> dependency -> target -> dependency2 + CHECK_THROWS(std::exception, + buildcc::Reg::Toolchain(trueState).Dep(dependency2, target)); + buildcc::Reg::Deinit(); + } + + buildcc::Project::Deinit(); + mock().checkExpectations(); +} + +TEST(RegisterTestGroup, Register_Test) { + // Arguments + std::vector av{ + "", + "--config", + "configs/basic_parse.toml", + }; + int argc = av.size(); + + buildcc::ArgToolchain gcc_toolchain; + buildcc::ArgToolchain msvc_toolchain; + buildcc::Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", gcc_toolchain) + .AddToolchain("msvc", "Generic msvc toolchain", msvc_toolchain) + .Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + // Make dummy toolchain and target + buildcc::Project::Init(fs::current_path(), fs::current_path()); + buildcc::Toolchain toolchain( + buildcc::ToolchainId::Gcc, "", + buildcc::ToolchainExecutables("", "", "", "", "")); + buildcc::BaseTarget target("dummyT", buildcc::TargetType::Executable, + toolchain, ""); + buildcc::BaseTarget dependency("depT", buildcc::TargetType::Executable, + toolchain, ""); + + // 4 states between build and test + // FF + // TF + // FT + // TT -> only success case + buildcc::ArgToolchainState stateFail{false, false}; + buildcc::ArgToolchainState state1{true, false}; + buildcc::ArgToolchainState state2{false, true}; + buildcc::ArgToolchainState stateSuccess{true, true}; + + // FF + { + buildcc::Reg::Init(); + buildcc::Reg::Toolchain(stateFail).Test("{executable}", target); + buildcc::Reg::Deinit(); + } + + // TF + { + buildcc::Reg::Init(); + buildcc::Reg::Toolchain(state1).Test("{executable}", target); + buildcc::Reg::Deinit(); + } + + // FT + { + buildcc::Reg::Init(); + buildcc::Reg::Toolchain(state2).Test("{executable}", target); + buildcc::Reg::Deinit(); + } + + // TT + // Reg::Instance::Build not called + { + buildcc::Reg::Init(); + CHECK_THROWS( + std::exception, + buildcc::Reg::Toolchain(stateSuccess).Test("{executable}", target)); + buildcc::Reg::Deinit(); + } + + // Correct Usage + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + buildcc::Reg::Toolchain(stateSuccess) + .Build([](buildcc::BaseTarget &target) { (void)target; }, target); + buildcc::Reg::Toolchain(stateSuccess).Test("{executable}", target); + + std::vector stdout_data; + std::vector stderr_data; + buildcc::env::m::CommandExpect_Execute(1, true, &stdout_data, &stderr_data); + buildcc::Reg::Run(); + buildcc::Reg::Deinit(); + } + + buildcc::Project::Deinit(); + mock().checkExpectations(); +} + +TEST(RegisterTestGroup, Register_TestWithOutput) { + // Arguments + std::vector av{ + "", + "--config", + "configs/basic_parse.toml", + }; + int argc = av.size(); + + buildcc::ArgToolchain gcc_toolchain; + buildcc::ArgToolchain msvc_toolchain; + buildcc::Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", gcc_toolchain) + .AddToolchain("msvc", "Generic msvc toolchain", msvc_toolchain) + .Parse(argc, av.data()); + + STRCMP_EQUAL(buildcc::Args::GetProjectRootDir().string().c_str(), "root"); + STRCMP_EQUAL(buildcc::Args::GetProjectBuildDir().string().c_str(), "build"); + CHECK(buildcc::Args::GetLogLevel() == buildcc::env::LogLevel::Trace); + CHECK_TRUE(buildcc::Args::Clean()); + + // Make dummy toolchain and target + buildcc::Project::Init(fs::current_path(), fs::current_path()); + buildcc::Toolchain toolchain( + buildcc::ToolchainId::Gcc, "", + buildcc::ToolchainExecutables("", "", "", "", "")); + buildcc::BaseTarget target("dummyT", buildcc::TargetType::Executable, + toolchain, ""); + buildcc::BaseTarget dependency("depT", buildcc::TargetType::Executable, + toolchain, ""); + + buildcc::ArgToolchainState stateSuccess{true, true}; + + // TestOutput::Type::DefaultBehaviour + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + buildcc::Reg::Toolchain(stateSuccess) + .Build([](buildcc::BaseTarget &target) { (void)target; }, target); + buildcc::Reg::Toolchain(stateSuccess) + .Test("{executable}", target, + buildcc::TestConfig( + {}, {}, + buildcc::TestOutput( + buildcc::TestOutput::Type::DefaultBehaviour))); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::Reg::Run(); + buildcc::Reg::Deinit(); + } + + // TestOutput::Type::TestPrintOnStderr + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + buildcc::Reg::Toolchain(stateSuccess) + .Build([](buildcc::BaseTarget &target) { (void)target; }, target); + buildcc::Reg::Toolchain(stateSuccess) + .Test("{executable}", target, + buildcc::TestConfig( + {}, {}, + buildcc::TestOutput( + buildcc::TestOutput::Type::TestPrintOnStderr))); + + std::vector stderr_data; + buildcc::env::m::CommandExpect_Execute(1, true, nullptr, &stderr_data); + buildcc::Reg::Run(); + buildcc::Reg::Deinit(); + } + + // TestOutput::Type::TestPrintOnStdout + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + buildcc::Reg::Toolchain(stateSuccess) + .Build([](buildcc::BaseTarget &target) { (void)target; }, target); + buildcc::Reg::Toolchain(stateSuccess) + .Test("{executable}", target, + buildcc::TestConfig( + {}, {}, + buildcc::TestOutput( + buildcc::TestOutput::Type::TestPrintOnStdout))); + + std::vector stdout_data; + buildcc::env::m::CommandExpect_Execute(1, true, &stdout_data, nullptr); + buildcc::Reg::Run(); + buildcc::Reg::Deinit(); + } + + // TestOutput::Type::TestPrintOnStderrAndStdout + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + buildcc::Reg::Toolchain(stateSuccess) + .Build([](buildcc::BaseTarget &target) { (void)target; }, target); + buildcc::Reg::Toolchain(stateSuccess) + .Test("{executable}", target, + buildcc::TestConfig( + {}, {}, + buildcc::TestOutput( + buildcc::TestOutput::Type::TestPrintOnStderrAndStdout))); + + std::vector stdout_data; + std::vector stderr_data; + buildcc::env::m::CommandExpect_Execute(1, true, &stdout_data, &stderr_data); + buildcc::Reg::Run(); + buildcc::Reg::Deinit(); + } + + // TestOutput::Type::UserRedirect + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + buildcc::Reg::Toolchain(stateSuccess) + .Build([](buildcc::BaseTarget &target) { (void)target; }, target); + buildcc::Reg::Toolchain(stateSuccess) + .Test("{executable}", target, + buildcc::TestConfig( + {}, {}, + buildcc::TestOutput(buildcc::TestOutput::Type::UserRedirect, + nullptr, nullptr))); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::Reg::Run(); + buildcc::Reg::Deinit(); + } + + // TestOutput::Type::UserRedirect + { + buildcc::Reg::Init(); + mock().expectNCalls(1, "BuildTask_dummyT"); + buildcc::Reg::Toolchain(stateSuccess) + .Build([](buildcc::BaseTarget &target) { (void)target; }, target); + buildcc::Reg::Toolchain(stateSuccess) + .Test( + "{executable}", target, + buildcc::TestConfig( + {}, {}, buildcc::TestOutput(buildcc::TestOutput::Type(65535)))); + CHECK_THROWS(std::exception, buildcc::Reg::Run()); + buildcc::Reg::Deinit(); + } + + buildcc::Project::Deinit(); + mock().checkExpectations(); +} + +int main(int ac, char **av) { + MemoryLeakWarningPlugin::destroyGlobalDetector(); + buildcc::env::m::VectorStringCopier copier; + mock().installCopier(TEST_VECTOR_STRING_TYPE, copier); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/env/CMakeLists.txt b/buildcc/lib/env/CMakeLists.txt index 3b167043..4bba5a42 100644 --- a/buildcc/lib/env/CMakeLists.txt +++ b/buildcc/lib/env/CMakeLists.txt @@ -1,46 +1,120 @@ # Env test if (${TESTING}) - add_library(mock_env STATIC) - target_sources(mock_env PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/mock/logging.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/env.cpp + add_library(mock_env STATIC + mock/logging.cpp + mock/assert_fatal.cpp + + src/env.cpp + src/task_state.cpp + src/storage.cpp + + src/command.cpp + mock/execute.cpp ) target_include_directories(mock_env PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/mock/include ) target_link_libraries(mock_env PUBLIC - fmt::fmt-header-only + fmt::fmt + tl::optional + Taskflow + + CppUTest + CppUTestExt + ${TEST_LINK_LIBS} ) + target_compile_options(mock_env PUBLIC ${TEST_COMPILE_FLAGS} ${BUILD_COMPILE_FLAGS}) + target_link_options(mock_env PUBLIC ${TEST_LINK_FLAGS} ${BUILD_LINK_FLAGS}) + + # Tests + add_executable(test_static_project test/test_static_project.cpp) + target_link_libraries(test_static_project PRIVATE mock_env) + + add_executable(test_env_util test/test_env_util.cpp) + target_link_libraries(test_env_util PRIVATE mock_env) + + add_executable(test_task_state test/test_task_state.cpp) + target_link_libraries(test_task_state PRIVATE mock_env) + + add_executable(test_command test/test_command.cpp) + target_link_libraries(test_command PRIVATE mock_env) + + add_executable(test_storage test/test_storage.cpp) + target_link_libraries(test_storage PRIVATE mock_env) + + add_executable(test_assert_fatal test/test_assert_fatal.cpp) + target_link_libraries(test_assert_fatal PRIVATE mock_env) + + add_test(NAME test_static_project COMMAND test_static_project) + add_test(NAME test_env_util COMMAND test_env_util) + add_test(NAME test_task_state COMMAND test_task_state) + add_test(NAME test_command COMMAND test_command) + add_test(NAME test_storage COMMAND test_storage) + add_test(NAME test_assert_fatal COMMAND test_assert_fatal) endif() -# Env lib -m_clangtidy("env") -add_library(env +set(ENV_SRCS + include/env/optional.h + src/env.cpp + src/assert_fatal.cpp src/logging.cpp - include/env.h - include/assert_fatal.h - include/logging.h -) -target_include_directories(env PUBLIC - $ - $ -) -target_link_libraries(env PUBLIC fmt::fmt-header-only) -target_compile_options(env PRIVATE ${BUILD_COMPILE_FLAGS}) -target_link_options(env PRIVATE ${BUILD_LINK_FLAGS}) -target_link_libraries(env PRIVATE - spdlog::spdlog_header_only + include/env/assert_fatal.h + include/env/env.h + include/env/logging.h + include/env/util.h + + include/env/host_os.h + include/env/host_compiler.h + include/env/host_os_util.h + + src/task_state.cpp + include/env/task_state.h + + src/command.cpp + src/execute.cpp + include/env/command.h + + src/storage.cpp + include/env/storage.h ) -# Env install +if(${BUILDCC_BUILD_AS_SINGLE_LIB}) + target_sources(buildcc PRIVATE + ${ENV_SRCS} + ) + target_include_directories(buildcc PUBLIC + $ + $ + ) +endif() + +if(${BUILDCC_BUILD_AS_INTERFACE}) + m_clangtidy("env") + add_library(env + ${ENV_SRCS} + ) + target_include_directories(env PUBLIC + $ + $ + ) + target_link_libraries(env PUBLIC + fmt::fmt + tl::optional + ) + target_compile_options(env PRIVATE ${BUILD_COMPILE_FLAGS}) + target_link_options(env PRIVATE ${BUILD_LINK_FLAGS}) + target_link_libraries(env PRIVATE + spdlog::spdlog + tiny-process-library::tiny-process-library + ) +endif() + if (${BUILDCC_INSTALL}) - install(TARGETS env DESTINATION lib EXPORT envConfig) - install(FILES - ${CMAKE_CURRENT_SOURCE_DIR}/include/assert_fatal.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/env.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/logging.h - DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}" - ) - install(EXPORT envConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/env") + if (${BUILDCC_BUILD_AS_INTERFACE}) + install(TARGETS env DESTINATION lib EXPORT envConfig) + install(EXPORT envConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/env") + endif() + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}") endif() diff --git a/buildcc/lib/env/include/env/assert_fatal.h b/buildcc/lib/env/include/env/assert_fatal.h new file mode 100644 index 00000000..9f048066 --- /dev/null +++ b/buildcc/lib/env/include/env/assert_fatal.h @@ -0,0 +1,78 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ENV_ASSERT_FATAL_H_ +#define ENV_ASSERT_FATAL_H_ + +#include + +#include "logging.h" + +namespace buildcc::env { + +/** + * @brief During Release -> + * NOT THREADED : std::exit + * THREADED : throw std::exception (it is wrong to exit + * when in a threaded state. We want to handle the exception and gracefully + * exit) + * During Unit Test -> throw std::exception + */ +[[noreturn]] void assert_handle_fatal(); + +/** + * @brief Compile time expr asserts fatally when false + */ +template +inline void assert_fatal([[maybe_unused]] const char *message) { + if constexpr (!expr) { + env::log_critical("assert", message); + assert_handle_fatal(); + } +} + +/** + * @brief Compile time expr asserts fatally when false + */ +template inline void assert_fatal(const std::string &message) { + assert_fatal(message.c_str()); +} + +/** + * @brief Runtime expr asserts fatally when false + */ +inline void assert_fatal(bool expression, const char *message) { + if (!expression) { + assert_fatal(message); + } +} + +/** + * @brief Runtime expr asserts fatally when false + */ +inline void assert_fatal(bool expression, const std::string &message) { + assert_fatal(expression, message.c_str()); +} + +} // namespace buildcc::env + +/** + * @brief Runtime expr asserts fatally when false + */ +#define ASSERT_FATAL(expr, message) \ + ((expr) ? static_cast(0) : buildcc::env::assert_fatal(message)) + +#endif diff --git a/buildcc/lib/env/include/env/command.h b/buildcc/lib/env/include/env/command.h new file mode 100644 index 00000000..c054e6b3 --- /dev/null +++ b/buildcc/lib/env/include/env/command.h @@ -0,0 +1,95 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ENV_COMMAND_H_ +#define ENV_COMMAND_H_ + +#include +#include +#include +#include + +#include "env/optional.h" + +namespace fs = std::filesystem; + +namespace buildcc::env { + +class Command { +public: + explicit Command() = default; + + /** + * @brief Add key-value pairs that are used by fmt as identifiers + * Example: {"key", "value"} -> fmt::format("{key}") -> "value" + * + * NOTE: These default arguments persist throughout the lifetime of the + * Command object + */ + void AddDefaultArgument(const std::string &key, const std::string &value); + + /** + * @brief Add multiple key-value pairs that are used by fmt as identifiers + * Example: {"key", "value"} -> fmt::format("{key}") -> "value" + * + * NOTE: These default arguments persist throughout the lifetime of the + * Command object + */ + void AddDefaultArguments( + const std::unordered_map &arguments); + + /** + * @brief Construct a specialized string using input pattern and supplied + * arguments + * + * NOTE: These arguments are only valid for the `Construct` function call + */ + std::string Construct(const std::string &pattern, + const std::unordered_map + &arguments = {}) const; + + /** + * @brief Execute a particular command over a subprocess and optionally + * redirect stdout and stderr to user supplied dynamic string lists + * + * @param command Command is run on the shell + * @param working_directory Current working directory + * @param stdout_data Redirect stdout to user OR default print to console + * @param stderr_data Redirect stderr to user OR default print to console + * @return true when exit code = 0 + * @return false when exit code != 0 + */ + // TODO, Update this to get an integer exit code number instead of boolean + // value + static bool Execute(const std::string &command, + const optional &working_directory = {}, + std::vector *stdout_data = nullptr, + std::vector *stderr_data = nullptr); + + /** + * @brief Get the Default Value By Key object + * NOTE: Only works when key/value pairs are added to DefaultArgument(s) + * Assert Fatal if default value is not found + */ + const std::string &GetDefaultValueByKey(const std::string &key) const; + +private: + std::unordered_map default_values_; +}; + +} // namespace buildcc::env + +#endif diff --git a/buildcc/lib/env/include/env/env.h b/buildcc/lib/env/include/env/env.h new file mode 100644 index 00000000..c260b28c --- /dev/null +++ b/buildcc/lib/env/include/env/env.h @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ENV_ENV_H_ +#define ENV_ENV_H_ + +#include + +#include + +namespace fs = std::filesystem; + +namespace buildcc { + +class Project { +public: + Project() = delete; + Project(const Project &) = delete; + Project(Project &&) = delete; + static void Init(const fs::path &project_root_dir, + const fs::path &project_build_dir); + static void Deinit(); + + static bool IsInit(); + static const fs::path &GetRootDir(); + static const fs::path &GetBuildDir(); + +private: + static bool &GetStaticInit(); + static fs::path &GetStaticRootDir(); + static fs::path &GetStaticBuildDir(); +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/env/include/env/host_compiler.h b/buildcc/lib/env/include/env/host_compiler.h new file mode 100644 index 00000000..0340fc9a --- /dev/null +++ b/buildcc/lib/env/include/env/host_compiler.h @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ENV_HOST_COMPILER_H_ +#define ENV_HOST_COMPILER_H_ + +// https://sourceforge.net/p/predef/wiki/OperatingSystems/ +// https://abseil.io/docs/cpp/platforms/macros +namespace buildcc::env { + +inline constexpr bool is_gcc() { +#if defined(__GNUC__) + return true; +#else + return false; +#endif +} + +inline constexpr bool is_mingw() { +#if defined(__MINGW32__) || defined(__MINGW64__) + return true; +#else + return false; +#endif +} + +inline constexpr bool is_clang() { +#if defined(__clang__) + return true; +#else + return false; +#endif +} + +inline constexpr bool is_msvc() { +#if defined(_MSC_VER) + return true; +#else + return false; +#endif +} + +} // namespace buildcc::env + +#endif diff --git a/buildcc/lib/env/include/env/host_os.h b/buildcc/lib/env/include/env/host_os.h new file mode 100644 index 00000000..9ec18ad6 --- /dev/null +++ b/buildcc/lib/env/include/env/host_os.h @@ -0,0 +1,92 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ENV_HOST_OS_H_ +#define ENV_HOST_OS_H_ + +// https://sourceforge.net/p/predef/wiki/OperatingSystems/ +// https://web.archive.org/web/20191012035921/http://nadeausoftware.com/articles/2012/01/c_c_tip_how_use_compiler_predefined_macros_detect_operating_system +// https://abseil.io/docs/cpp/platforms/macros +namespace buildcc::env { + +inline constexpr bool is_linux() { +#if defined(__linux__) + return true; +#else + return false; +#endif +} + +inline constexpr bool is_win() { +#if defined(_WIN32) + return true; +#else + return false; +#endif +} + +inline constexpr bool is_mac() { +#if defined(__APPLE__) || defined(__MACH__) + return true; +#else + return false; +#endif +} + +inline constexpr bool is_unix() { +#if defined(__unix__) + return true; +#else + return false; +#endif +} + +} // namespace buildcc::env + +namespace buildcc { + +enum class OsId { + Linux, + Win, + Mac, + Unix, + Undefined, +}; + +inline constexpr OsId get_host_os() { + OsId os_id = OsId::Undefined; + if constexpr (env::is_linux()) { + os_id = OsId::Linux; + } + + if constexpr (env::is_unix()) { + os_id = OsId::Unix; + } + + if constexpr (env::is_win()) { + os_id = OsId::Win; + } + + if constexpr (env::is_mac()) { + os_id = OsId::Mac; + } + + return os_id; +} + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/env/include/env/host_os_util.h b/buildcc/lib/env/include/env/host_os_util.h new file mode 100644 index 00000000..a65e46c1 --- /dev/null +++ b/buildcc/lib/env/include/env/host_os_util.h @@ -0,0 +1,80 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ENV_HOST_OS_UTIL_H_ +#define ENV_HOST_OS_UTIL_H_ + +#include + +#include "host_os.h" +#include "logging.h" + +// https://askubuntu.com/questions/156392/what-is-the-equivalent-of-an-exe-file +namespace buildcc::env { + +// Common +constexpr const char *const kRaiseIssueStr = + "Unknown operating system, returning nullptr. Raise an " + "issue at http://github.com/coder137/build_in_cpp"; + +// OS Path delimiters +constexpr const char *const kWinEnvDelim = ";"; +constexpr const char *const kUnixEnvDelim = ":"; + +/** + * @brief Get the OS environment variable delimiter + * ; for windows + * : for linux, unix and mac + * + * @return constexpr char const* for supported operating systems + * @return nullptr otherwise with a critical message to raise an issue + */ +inline constexpr char const *get_os_envvar_delim() { + if constexpr (is_win()) { + return kWinEnvDelim; + } else if constexpr (is_linux() || is_unix() || is_mac()) { + return kUnixEnvDelim; + } + log_critical(__FUNCTION__, kRaiseIssueStr); + return nullptr; +} + +// OS executable extensions +constexpr const char *const kWinExecutableExt = ".exe"; +constexpr const char *const kUnixExecutableExt = ""; + +/** + * @brief Get the OS executable extension + * ".exe" for windows + * "" for linux, unix and mac + * + * @return constexpr const char* for supported operating systems + * @return nullptr otherwise with a critical message to raise an issue + */ +inline constexpr const char *get_os_executable_extension() { + if constexpr (is_win()) { + return kWinExecutableExt; + } else if constexpr (is_linux() || is_unix() || is_mac()) { + return kUnixExecutableExt; + } + + log_critical(__FUNCTION__, kRaiseIssueStr); + return nullptr; +} + +} // namespace buildcc::env + +#endif diff --git a/buildcc/lib/env/include/logging.h b/buildcc/lib/env/include/env/logging.h similarity index 92% rename from buildcc/lib/env/include/logging.h rename to buildcc/lib/env/include/env/logging.h index dc709ca8..865721bc 100644 --- a/buildcc/lib/env/include/logging.h +++ b/buildcc/lib/env/include/env/logging.h @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ENV_INCLUDE_LOGGING_H_ -#define ENV_INCLUDE_LOGGING_H_ +#ifndef ENV_LOGGING_H_ +#define ENV_LOGGING_H_ #include diff --git a/buildcc/lib/env/include/env.h b/buildcc/lib/env/include/env/optional.h similarity index 52% rename from buildcc/lib/env/include/env.h rename to buildcc/lib/env/include/env/optional.h index 7bd5cdd2..ed594535 100644 --- a/buildcc/lib/env/include/env.h +++ b/buildcc/lib/env/include/env/optional.h @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,30 +14,14 @@ * limitations under the License. */ -#ifndef ENV_INCLUDE_ENV_H_ -#define ENV_INCLUDE_ENV_H_ +#ifndef ENV_OPTIONAL_H_ +#define ENV_OPTIONAL_H_ -#include - -#include - -namespace fs = std::filesystem; +#include "tl/optional.hpp" namespace buildcc::env { -/** - * @brief Initialize project environment - * - * @param project_root_dir Root directory for source files - * @param project_build_dir Directory for intermediate build files - */ -void init(const fs::path &project_root_dir, const fs::path &project_build_dir); -void deinit(); - -// Getters -bool is_init(); -const fs::path &get_project_root_dir(); -const fs::path &get_project_build_dir(); +template using optional = tl::optional; } // namespace buildcc::env diff --git a/buildcc/lib/env/include/env/storage.h b/buildcc/lib/env/include/env/storage.h new file mode 100644 index 00000000..b5407df8 --- /dev/null +++ b/buildcc/lib/env/include/env/storage.h @@ -0,0 +1,154 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ENV_STORAGE_H_ +#define ENV_STORAGE_H_ + +#include +#include +#include +#include +#include + +#include "env/assert_fatal.h" + +#include "fmt/format.h" + +namespace buildcc { + +class ScopedStorage { +public: + ScopedStorage() {} + ~ScopedStorage() { Clear(); } + + ScopedStorage(const ScopedStorage &) = delete; + + template + T &Add(const std::string &identifier, Params &&...params) { + T *ptr = new T(std::forward(params)...); + env::assert_fatal(ptr != nullptr, "System out of memory"); + + PtrMetadata metadata; + metadata.ptr = (void *)ptr; + metadata.typeid_name = typeid(T).name(); + metadata.destructor = [this, identifier, ptr]() { + env::log_trace("Cleaning", identifier); + Remove(ptr); + }; + ptrs_.emplace(identifier, metadata); + return *ptr; + } + + void Clear() { + for (const auto &ptr_iter : ptrs_) { + ptr_iter.second.destructor(); + } + ptrs_.clear(); + } + + template const T &ConstRef(const std::string &identifier) const { + env::assert_fatal(Contains(identifier), + fmt::format("Could not find '{}'", identifier)); + const PtrMetadata &metadata = ptrs_.at(identifier); + env::assert_fatal( + typeid(T).name() == metadata.typeid_name, + fmt::format("Wrong type, expects: {}", metadata.typeid_name)); + const T *p = (const T *)metadata.ptr; + return *p; + } + + // https://stackoverflow.com/questions/123758/how-do-i-remove-code-duplication-between-similar-const-and-non-const-member-func/123995 + template T &Ref(const std::string &identifier) { + return const_cast( + static_cast(*this).ConstRef(identifier)); + } + + bool Contains(const std::string &identifier) const { + return (ptrs_.find(identifier) != ptrs_.end()); + } + + template bool Valid(const std::string &identifier) const { + if (!Contains(identifier)) { + return false; + } + const PtrMetadata &metadata = ptrs_.at(identifier); + if (typeid(T).name() != metadata.typeid_name) { + return false; + } + return true; + } + +protected: + template void Remove(T *ptr) { delete ptr; } + +private: + /** + * @brief + * @param ptr Can hold data of any type + * @param typeid_name We cannot store a template type so this is the next + * best thing + * @param destructor Destructor callback to delete ptr + */ + struct PtrMetadata { + void *ptr{nullptr}; + std::string typeid_name; + std::function destructor; + }; + +private: + std::unordered_map ptrs_; +}; + +class Storage { +public: + Storage() = delete; + Storage(const Storage &) = delete; + Storage(Storage &&) = delete; + + template + static T &Add(const std::string &identifier, Params &&...params) { + return Ref().Add(identifier, std::forward(params)...); + } + + static void Clear() { Ref().Clear(); } + + template + static const T &ConstRef(const std::string &identifier) { + return Ref().ConstRef(identifier); + } + + template static T &Ref(const std::string &identifier) { + return Ref().Ref(identifier); + } + + static bool Contains(const std::string &identifier) { + return Ref().Contains(identifier); + } + + template static bool Valid(const std::string &identifier) { + return Ref().Valid(identifier); + } + +private: + static ScopedStorage &Ref(); + +private: + static ScopedStorage internal_; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/env/include/env/task_state.h b/buildcc/lib/env/include/env/task_state.h new file mode 100644 index 00000000..629a8aee --- /dev/null +++ b/buildcc/lib/env/include/env/task_state.h @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ENV_TASK_STATE_H_ +#define ENV_TASK_STATE_H_ + +namespace buildcc::env { + +enum class TaskState { + SUCCESS, + FAILURE, + // TODO, Add more states here +}; + +void set_task_state(TaskState state); +TaskState get_task_state(); + +} // namespace buildcc::env + +#endif diff --git a/buildcc/lib/env/include/env/util.h b/buildcc/lib/env/include/env/util.h new file mode 100644 index 00000000..45a71448 --- /dev/null +++ b/buildcc/lib/env/include/env/util.h @@ -0,0 +1,158 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ENV_UTIL_H_ +#define ENV_UTIL_H_ + +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace buildcc::env { + +/** + * Condition under which code throws and should terminate + * 1: ofs.write -> badbit + */ +inline bool save_file(const char *name, const char *buf, size_t len, + bool binary) { + if (name == nullptr || buf == nullptr) { + return false; + } + std::ofstream ofs(name, binary ? std::ofstream::binary : std::ofstream::out); + if (!ofs.is_open()) { + return false; + } + + // * 1 + std::ostream &os = ofs.write(buf, len); + return !os.bad(); +} + +inline bool save_file(const char *name, const std::string &buf, bool binary) { + return save_file(name, buf.c_str(), buf.size(), binary); +} + +/** + * Condition under which code throws and should terminate + * 1: fs::file_size -> filesystem_error, bad_alloc error + * 2: resize -> length_error, bad_alloc error + * 3: ifs.read -> badbit + * 4: + */ +inline bool load_file(const char *name, bool binary, std::string *buf) { + if (name == nullptr || buf == nullptr) { + return false; + } + std::error_code errcode; + if (fs::is_directory(name, errcode)) { + return false; + } + if (errcode) { + return false; + } + std::ifstream ifs(name, binary ? std::ifstream::binary : std::ifstream::in); + if (!ifs.is_open()) { + return false; + } + if (binary) { + // The fastest way to read a file into a string. + // If we cannot get the file size we should terminate + // * 1 + auto size = static_cast(fs::file_size(name)); + // * 2 + buf->resize(size); + // * 3 + // flawfinder: ignore + ifs.read(buf->data(), buf->size()); + } else { + // This is slower, but works correctly on all platforms for text files. + // * 4 + std::ostringstream oss; + oss << ifs.rdbuf(); + *buf = oss.str(); + } + return !ifs.bad(); +} + +// TODO, Shift this to a common apis folder or something +// https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c +inline std::vector split(const std::string &s, char delim) { + std::vector result; + std::stringstream ss(s); + std::string item; + while (getline(ss, item, delim)) { + result.push_back(item); + } + + return result; +} + +// https://stackoverflow.com/questions/44973435/stdptr-fun-replacement-for-c17/44973498#44973498 +inline std::string ltrim(const std::string &s) { + std::string tr{s}; + // Checks from left (beginning) and finds the `end point` where no `isspace` + // char is detected + auto l_tr_end = std::find_if(tr.begin(), tr.end(), + [](char c) { return !std::isspace(c); }); + tr.erase(tr.begin(), l_tr_end); + return tr; +} + +inline std::string rtrim(const std::string &s) { + std::string tr{s}; + // Checks from right (ending) and finds the `start point` where no `isspace` + // char is detected + // Gets the base iterator (which is forward in nature) + auto r_tr_begin = std::find_if(tr.rbegin(), tr.rend(), [](char c) { + return !std::isspace(c); + }).base(); + tr.erase(r_tr_begin, tr.end()); + return tr; +} + +/** + * @brief Trims both left and right part of the string + */ +inline std::string trim(const std::string &s) { + std::string tr = ltrim(s); + tr = rtrim(tr); + return tr; +} + +} // namespace buildcc::env + +#endif diff --git a/buildcc/lib/env/mock/assert_fatal.cpp b/buildcc/lib/env/mock/assert_fatal.cpp new file mode 100644 index 00000000..08d1b462 --- /dev/null +++ b/buildcc/lib/env/mock/assert_fatal.cpp @@ -0,0 +1,9 @@ +#include "env/assert_fatal.h" + +#include + +namespace buildcc::env { + +[[noreturn]] void assert_handle_fatal() { throw std::exception(); } + +} // namespace buildcc::env diff --git a/buildcc/lib/env/mock/execute.cpp b/buildcc/lib/env/mock/execute.cpp new file mode 100644 index 00000000..7fc7f59b --- /dev/null +++ b/buildcc/lib/env/mock/execute.cpp @@ -0,0 +1,51 @@ +#include "env/command.h" + +#include "expect_command.h" + +#include "CppUTestExt/MockSupport.h" + +namespace buildcc::env { + +static constexpr const char *const EXECUTE_FUNCTION = "execute"; +static constexpr const char *const STDOUT_DATA_STRING = "stdout_data"; +static constexpr const char *const STDERR_DATA_STRING = "stderr_data"; + +// command +bool Command::Execute(const std::string &command, + const optional &working_directory, + std::vector *stdout_data, + std::vector *stderr_data) { + (void)command; + (void)working_directory; + auto &actualcall = mock().actualCall(EXECUTE_FUNCTION); + if (stdout_data != nullptr) { + actualcall.withOutputParameterOfType( + TEST_VECTOR_STRING_TYPE, STDOUT_DATA_STRING, (void *)stdout_data); + } + if (stderr_data != nullptr) { + actualcall.withOutputParameterOfType( + TEST_VECTOR_STRING_TYPE, STDERR_DATA_STRING, (void *)stderr_data); + } + return actualcall.returnBoolValue(); +} + +namespace m { + +void CommandExpect_Execute(unsigned int calls, bool expectation, + std::vector *stdout_data, + std::vector *stderr_data) { + auto &expectedcall = mock().expectNCalls(calls, EXECUTE_FUNCTION); + if (stdout_data != nullptr) { + expectedcall.withOutputParameterOfTypeReturning( + TEST_VECTOR_STRING_TYPE, STDOUT_DATA_STRING, (void *)stdout_data); + } + if (stderr_data != nullptr) { + expectedcall.withOutputParameterOfTypeReturning( + TEST_VECTOR_STRING_TYPE, STDERR_DATA_STRING, (void *)stderr_data); + } + expectedcall.andReturnValue(expectation); +} + +} // namespace m + +} // namespace buildcc::env diff --git a/buildcc/lib/env/mock/include/expect_command.h b/buildcc/lib/env/mock/include/expect_command.h new file mode 100644 index 00000000..6a65a4fc --- /dev/null +++ b/buildcc/lib/env/mock/include/expect_command.h @@ -0,0 +1,28 @@ +#ifndef ENV_MOCK_EXPECT_COMMAND_H_ +#define ENV_MOCK_EXPECT_COMMAND_H_ + +#include +#include + +constexpr const char *const TEST_VECTOR_STRING_TYPE = "vector_string"; + +namespace buildcc::env::m { + +/** + * @brief `Command::Execute` expectation API + * + * @param calls Number of times the actual `Command::Execute` API is called + * @param expectation Return value of the actual `Command::Execute` API + * @param stdout_data Data passed into stdout_data is redirected into the + * actual `Command::Execute` API to check for expectations. See + * `VectorStringCopier` + * @param stderr_data Data passed into stderr_data is redirected into the actual + * `Command::Execute` API to check for expectations. See `VectorStringCopier` + */ +void CommandExpect_Execute(unsigned int calls, bool expectation, + std::vector *stdout_data = nullptr, + std::vector *stderr_data = nullptr); + +} // namespace buildcc::env::m + +#endif diff --git a/buildcc/lib/env/mock/include/mock_command_copier.h b/buildcc/lib/env/mock/include/mock_command_copier.h new file mode 100644 index 00000000..f9e0e70c --- /dev/null +++ b/buildcc/lib/env/mock/include/mock_command_copier.h @@ -0,0 +1,28 @@ +#ifndef ENV_MOCK_MOCK_COMMAND_COPIER_H_ +#define ENV_MOCK_MOCK_COMMAND_COPIER_H_ + +#include +#include + +#include "CppUTestExt/MockSupport.h" + +namespace buildcc::env::m { + +class VectorStringCopier : public MockNamedValueCopier { +public: + void copy(void *out, const void *in) override { + const std::vector *vin = (const std::vector *)in; + std::vector *vout = (std::vector *)out; + + if (vout == nullptr || vin == nullptr) { + return; + } + + vout->clear(); + vout->insert(vout->end(), vin->begin(), vin->end()); + } +}; + +} // namespace buildcc::env::m + +#endif diff --git a/buildcc/lib/env/mock/logging.cpp b/buildcc/lib/env/mock/logging.cpp index 64a780b3..1dd61c9d 100644 --- a/buildcc/lib/env/mock/logging.cpp +++ b/buildcc/lib/env/mock/logging.cpp @@ -1,4 +1,4 @@ -#include "logging.h" +#include "env/logging.h" // Stubs namespace buildcc::env { diff --git a/buildcc/lib/env/src/assert_fatal.cpp b/buildcc/lib/env/src/assert_fatal.cpp new file mode 100644 index 00000000..c1f6dd62 --- /dev/null +++ b/buildcc/lib/env/src/assert_fatal.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "env/assert_fatal.h" + +#include +#include + +namespace { + +std::thread::id main_id = std::this_thread::get_id(); + +bool IsRunningOnThread() { + bool threaded = true; + if (std::this_thread::get_id() == main_id) { + threaded = false; + } + return threaded; +} + +} // namespace + +namespace buildcc::env { + +[[noreturn]] void assert_handle_fatal() { + if (!IsRunningOnThread()) { + std::exit(1); + } else { + throw std::exception(); + } +} + +} // namespace buildcc::env diff --git a/buildcc/lib/env/src/command.cpp b/buildcc/lib/env/src/command.cpp new file mode 100644 index 00000000..2962aa24 --- /dev/null +++ b/buildcc/lib/env/src/command.cpp @@ -0,0 +1,74 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "env/command.h" + +#include + +#include "fmt/args.h" +#include "fmt/format.h" + +#include "env/assert_fatal.h" +#include "env/logging.h" + +namespace buildcc::env { + +void Command::AddDefaultArgument(const std::string &key, + const std::string &value) { + default_values_.emplace(key, value); +} + +void Command::AddDefaultArguments( + const std::unordered_map &arguments) { + default_values_.insert(arguments.begin(), arguments.end()); +} + +const std::string &Command::GetDefaultValueByKey(const std::string &key) const { + const auto iter = default_values_.find(key); + env::assert_fatal(!(iter == default_values_.end()), + fmt::format("Could not find value for '{}'", key)); + return default_values_.at(key); +} + +std::string Command::Construct( + const std::string &pattern, + const std::unordered_map &arguments) const { + // Construct your arguments + fmt::dynamic_format_arg_store store; + std::for_each(default_values_.cbegin(), default_values_.cend(), + [&store](const std::pair &p) { + store.push_back(fmt::arg(p.first.c_str(), p.second)); + }); + + std::for_each(arguments.cbegin(), arguments.cend(), + [&store](const std::pair &p) { + env::assert_fatal(p.first != NULL, + "Argument must not be NULL"); + store.push_back(fmt::arg(p.first, p.second)); + }); + + // Construct your command + std::string ret; + try { + ret = fmt::vformat(pattern, store); + } catch (const std::exception &e) { + env::assert_fatal( + fmt::format("Construct command failed: {}", e.what())); + } + return ret; +} + +} // namespace buildcc::env diff --git a/buildcc/lib/env/src/env.cpp b/buildcc/lib/env/src/env.cpp index 5e063114..64ed8404 100644 --- a/buildcc/lib/env/src/env.cpp +++ b/buildcc/lib/env/src/env.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,42 +14,48 @@ * limitations under the License. */ -#include "env.h" +#include "env/env.h" +#include "env/logging.h" -#include "logging.h" +namespace buildcc { -namespace { - -fs::path root_dir_{""}; -fs::path build_dir_{""}; -bool init_ = false; - -} // namespace - -namespace buildcc::env { - -void init(const fs::path &project_root_dir, const fs::path &project_build_dir) { +void Project::Init(const fs::path &project_root_dir, + const fs::path &project_build_dir) { // State - root_dir_ = project_root_dir; - build_dir_ = project_build_dir; - root_dir_.make_preferred(); - build_dir_.make_preferred(); + fs::path root_dir = project_root_dir; + fs::path build_dir = project_build_dir; + root_dir.make_preferred(); + build_dir.make_preferred(); - init_ = true; + GetStaticRootDir() = root_dir; + GetStaticBuildDir() = build_dir; + GetStaticInit() = true; // Logging - set_log_pattern("%^[%l]%$ %v"); - set_log_level(LogLevel::Info); + env::set_log_pattern("%^[%l]%$ %v"); + env::set_log_level(env::LogLevel::Info); } - -void deinit() { - root_dir_ = ""; - build_dir_ = ""; - init_ = false; +void Project::Deinit() { + GetStaticRootDir() = ""; + GetStaticBuildDir() = ""; + GetStaticInit() = false; } -bool is_init(void) { return init_; } -const fs::path &get_project_root_dir() { return root_dir_; } -const fs::path &get_project_build_dir() { return build_dir_; } +bool Project::IsInit() { return GetStaticInit(); } +const fs::path &Project::GetRootDir() { return GetStaticRootDir(); } +const fs::path &Project::GetBuildDir() { return GetStaticBuildDir(); } + +bool &Project::GetStaticInit() { + static bool is_init = false; + return is_init; +} +fs::path &Project::GetStaticRootDir() { + static fs::path root_dir = ""; + return root_dir; +} +fs::path &Project::GetStaticBuildDir() { + static fs::path build_dir = ""; + return build_dir; +} -} // namespace buildcc::env +} // namespace buildcc diff --git a/buildcc/lib/env/src/execute.cpp b/buildcc/lib/env/src/execute.cpp new file mode 100644 index 00000000..4281d442 --- /dev/null +++ b/buildcc/lib/env/src/execute.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "env/command.h" + +#include "fmt/format.h" + +#include "env/assert_fatal.h" +#include "env/host_os.h" +#include "env/logging.h" + +#include "process.hpp" + +namespace tpl = TinyProcessLib; + +namespace { + +tpl::Process::string_type get_working_directory( + const buildcc::env::optional &working_directory) { +#ifdef UNICODE + return working_directory.value_or(tpl::Process::string_type()).wstring(); +#else + return working_directory.value_or(tpl::Process::string_type()).string(); +#endif +} + +} // namespace + +namespace buildcc::env { + +bool Command::Execute(const std::string &command, + const optional &working_directory, + std::vector *stdout_data, + std::vector *stderr_data) { + env::assert_fatal(!command.empty(), "Empty command"); + buildcc::env::log_debug("system", command); + + std::function stdout_func = + [&](const char *bytes, size_t n) { + stdout_data->emplace_back(std::string(bytes, n)); + }; + + std::function stderr_func = + [&](const char *bytes, size_t n) { + stderr_data->emplace_back(std::string(bytes, n)); + }; + + tpl::Process process(command, get_working_directory(working_directory), + stdout_data == nullptr ? nullptr : stdout_func, + stderr_data == nullptr ? nullptr : stderr_func); + return process.get_exit_status() == 0; +} + +} // namespace buildcc::env diff --git a/buildcc/lib/env/src/logging.cpp b/buildcc/lib/env/src/logging.cpp index 4ebc7d71..a532f7b8 100644 --- a/buildcc/lib/env/src/logging.cpp +++ b/buildcc/lib/env/src/logging.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "logging.h" +#include "env/logging.h" #include "spdlog/spdlog.h" diff --git a/buildcc/lib/env/src/storage.cpp b/buildcc/lib/env/src/storage.cpp new file mode 100644 index 00000000..0da3ac5e --- /dev/null +++ b/buildcc/lib/env/src/storage.cpp @@ -0,0 +1,25 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "env/storage.h" + +namespace buildcc { + +ScopedStorage Storage::internal_; + +ScopedStorage &Storage::Ref() { return internal_; } + +} // namespace buildcc diff --git a/buildcc/lib/env/include/assert_fatal.h b/buildcc/lib/env/src/task_state.cpp similarity index 51% rename from buildcc/lib/env/include/assert_fatal.h rename to buildcc/lib/env/src/task_state.cpp index 17d2e05a..d0b14a64 100644 --- a/buildcc/lib/env/include/assert_fatal.h +++ b/buildcc/lib/env/src/task_state.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,33 +14,28 @@ * limitations under the License. */ -#ifndef ENV_INCLUDE_ASSERT_FATAL_H_ -#define ENV_INCLUDE_ASSERT_FATAL_H_ +#include "env/task_state.h" -#include +#include -#include "logging.h" +namespace { -namespace buildcc::env { - -class assert_exception : public std::exception { -public: - assert_exception(const char *const message) : message_(message) {} +std::mutex current_state_mutex; +buildcc::env::TaskState current_state{buildcc::env::TaskState::SUCCESS}; -private: - virtual const char *what() const throw() { return message_; } +} // namespace -private: - const char *const message_; -}; +namespace buildcc::env { -inline void assert_fatal(bool expression, const std::string &message) { - if (!expression) { - buildcc::env::log_critical("assert", message); - throw assert_exception(message.c_str()); +void set_task_state(TaskState state) { + // NOTE, `Avoid resetting` if same state is provided + if (state == get_task_state()) { + return; } + std::lock_guard guard(current_state_mutex); + current_state = state; } -} // namespace buildcc::env +TaskState get_task_state() { return current_state; } -#endif +} // namespace buildcc::env diff --git a/buildcc/lib/env/test/test_assert_fatal.cpp b/buildcc/lib/env/test/test_assert_fatal.cpp new file mode 100644 index 00000000..67e3b986 --- /dev/null +++ b/buildcc/lib/env/test/test_assert_fatal.cpp @@ -0,0 +1,72 @@ +#include + +#include "taskflow/taskflow.hpp" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +namespace { + +std::thread::id my_main_thread = std::this_thread::get_id(); + +bool IsRunningInThread() { + bool threaded = true; + if (std::this_thread::get_id() == my_main_thread) { + threaded = false; + } + return threaded; +} + +void assert_handle_fatal() { + if (IsRunningInThread()) { + mock().actualCall("assert_handle_fatal_threaded"); + } else { + mock().actualCall("assert_handle_fatal_main"); + } +} + +} // namespace + +// clang-format off +TEST_GROUP(AssertFatalTestGroup) +{ + void teardown() { + mock().clear(); + } +}; +// clang-format on + +TEST(AssertFatalTestGroup, AssertFatal_IsThreadedCheck) { + CHECK_FALSE(IsRunningInThread()); + + tf::Taskflow tf; + tf.emplace([]() { CHECK_TRUE(IsRunningInThread()); }); + + tf::Executor ex(1); + ex.run(tf); + ex.wait_for_all(); +} + +TEST(AssertFatalTestGroup, AssertFatal_Threaded) { + mock().expectOneCall("assert_handle_fatal_threaded"); + + tf::Taskflow tf; + tf.emplace([]() { assert_handle_fatal(); }); + + tf::Executor ex(1); + ex.run(tf); + ex.wait_for_all(); +} + +TEST(AssertFatalTestGroup, AssertFatal_NotThreaded) { + mock().expectOneCall("assert_handle_fatal_main"); + assert_handle_fatal(); +} + +int main(int ac, char **av) { + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/env/test/test_command.cpp b/buildcc/lib/env/test/test_command.cpp new file mode 100644 index 00000000..e1e1684c --- /dev/null +++ b/buildcc/lib/env/test/test_command.cpp @@ -0,0 +1,60 @@ +#include "env/command.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" + +// clang-format off +TEST_GROUP(CommandTestGroup) +{ +}; +// clang-format on + +TEST(CommandTestGroup, Construct_InvalidFormat) { + buildcc::env::Command command; + CHECK_THROWS(std::exception, command.Construct("{test}")); +} + +TEST(CommandTestGroup, Construct_Basic) { + buildcc::env::Command command; + std::string s = command.Construct("{}", {{"", "hi"}}); + STRCMP_EQUAL(s.c_str(), "hi"); +} + +TEST(CommandTestGroup, Construct_MultipleArgs) { + buildcc::env::Command command; + command.AddDefaultArguments({ + {"h", "hello"}, + {"w", "world"}, + }); + std::string s = command.Construct("{h} {w} {f} {c}", { + {"f", "from"}, + {"c", "coder137"}, + }); + STRCMP_EQUAL(s.c_str(), "hello world from coder137"); +} + +TEST(CommandTestGroup, Construct_BadArguments) { + buildcc::env::Command command; + CHECK_THROWS(std::exception, command.Construct("{}", {{nullptr, "hi"}})); +} + +TEST(CommandTestGroup, GetDefaultValueByKey) { + buildcc::env::Command command; + command.AddDefaultArgument("key", "value"); + const std::string &value = command.GetDefaultValueByKey("key"); + + STRCMP_EQUAL(value.c_str(), "value"); +} + +TEST(CommandTestGroup, GetDefaultValueByKey_BadKey) { + buildcc::env::Command command; + command.AddDefaultArgument("key", "value"); + CHECK_THROWS(std::exception, command.GetDefaultValueByKey("bad_key")); +} + +int main(int ac, char **av) { + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/env/test/test_env_util.cpp b/buildcc/lib/env/test/test_env_util.cpp new file mode 100644 index 00000000..3e6d867d --- /dev/null +++ b/buildcc/lib/env/test/test_env_util.cpp @@ -0,0 +1,190 @@ +#include "env/util.h" + +#include + +#include "env/host_os.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" + +// clang-format off +TEST_GROUP(EnvUtilTestGroup) +{ +}; +// clang-format on + +// save_file + +TEST(EnvUtilTestGroup, Util_SaveFile_NullptrInput) { + constexpr const char *const FILENAME = "NullptrInput.txt"; + fs::remove(FILENAME); + + const char *data = nullptr; + bool save = buildcc::env::save_file(FILENAME, data, 1, false); + CHECK_FALSE(save); +} + +TEST(EnvUtilTestGroup, Util_SaveFile_NullptrName) { + const char *filename = nullptr; + bool save = buildcc::env::save_file(filename, "Hello", false); + CHECK_FALSE(save); +} + +TEST(EnvUtilTestGroup, Util_SaveFile_GoodWrite) { + constexpr const char *const FILENAME = "GoodWrite.txt"; + fs::remove(FILENAME); + bool save = buildcc::env::save_file(FILENAME, "Hello", false); + CHECK_TRUE(save); +} + +TEST(EnvUtilTestGroup, Util_SaveFile_GoodWrite_Binary) { + constexpr const char *const FILENAME = "GoodWrite_Binary.txt"; + fs::remove(FILENAME); + bool save = buildcc::env::save_file(FILENAME, "Hello", true); + CHECK_TRUE(save); +} + +TEST(EnvUtilTestGroup, Util_SaveFile_CheckDirectory) { + // NOTE, This is a directory + constexpr const char *const DIRNAME = "my_random_directory"; + fs::create_directory(DIRNAME); + bool save = buildcc::env::save_file(DIRNAME, "Hello", true); + CHECK_FALSE(save); +} + +// Load File +TEST(EnvUtilTestGroup, Util_LoadFile_CheckDirectory) { + // NOTE, This is a directory + constexpr const char *const DIRNAME = "my_random_directory"; + fs::create_directory(DIRNAME); + std::string str; + bool load = buildcc::env::load_file(DIRNAME, false, &str); + std::cout << str << std::endl; + CHECK_FALSE(load); +} + +TEST(EnvUtilTestGroup, Util_LoadFile_NullptrName) { + const char *filename = nullptr; + std::string str; + bool load = buildcc::env::load_file(filename, false, &str); + CHECK_FALSE(load); +} + +TEST(EnvUtilTestGroup, Util_LoadFile_NullptrBuf) { + constexpr const char *const FILENAME = "NullptrBuf.txt"; + + std::string *str = nullptr; + bool load = buildcc::env::load_file(FILENAME, false, str); + CHECK_FALSE(load); +} + +TEST(EnvUtilTestGroup, Util_LoadFile_NullptrBufAndName) { + const char *filename = nullptr; + std::string *str = nullptr; + bool load = buildcc::env::load_file(filename, false, str); + CHECK_FALSE(load); +} + +TEST(EnvUtilTestGroup, Util_LoadFile_ReadBinary) { + constexpr const char *const FILENAME = "ReadBinary.txt"; + + char data[] = {0x00, 0x01, 0x02, 0x03, 0x04}; + bool save = buildcc::env::save_file(FILENAME, data, sizeof(data), true); + CHECK_TRUE(save); + + std::string str; + bool load = buildcc::env::load_file(FILENAME, true, &str); + MEMCMP_EQUAL(data, str.data(), sizeof(data)); + CHECK_TRUE(load); +} + +TEST(EnvUtilTestGroup, Util_LoadFile_ReadTxt) { + constexpr const char *const FILENAME = "ReadTxt.txt"; + + bool save = buildcc::env::save_file(FILENAME, "ReadTxt", false); + CHECK_TRUE(save); + + std::string str; + bool load = buildcc::env::load_file(FILENAME, false, &str); + STRCMP_EQUAL(str.c_str(), "ReadTxt"); + CHECK_TRUE(load); +} + +#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(__MINGW64__) + +TEST(EnvUtilTestGroup, Util_LoadFile_CannotOpen) { + constexpr const char *const FILENAME = "CannotOpen.txt"; + buildcc::env::save_file(FILENAME, "Random Data", false); + + // Remove read permission + std::error_code err; + fs::permissions(FILENAME, fs::perms::none, err); + if (err) { + FAIL("Cannot disable file permissions"); + } + + std::string str; + bool load = buildcc::env::load_file(FILENAME, true, &str); + CHECK_FALSE(load); +} + +TEST(EnvUtilTestGroup, Util_SaveFile_BadWrite_Binary) { + constexpr const char *const FILENAME = "BadWrite_Binary.txt"; + fs::remove(FILENAME); + bool save = buildcc::env::save_file(FILENAME, "Hello", -1, true); + CHECK_FALSE(save); +} + +TEST(EnvUtilTestGroup, Util_SaveFile_BadWrite) { + constexpr const char *const FILENAME = "BadWrite.txt"; + fs::remove(FILENAME); + bool save = buildcc::env::save_file(FILENAME, "Hello", -1, false); + CHECK_FALSE(save); +} + +TEST(EnvUtilTestGroup, Util_SaveFile_CannotWrite) { + constexpr const char *const FILENAME = "CannotWrite.txt"; + fs::remove(FILENAME); + bool save = buildcc::env::save_file(FILENAME, "Hello", false); + CHECK_TRUE(save); + + std::error_code err; + fs::permissions(FILENAME, fs::perms::none, err); + if (err) { + FAIL("Cannot disable file permissions"); + } + + save = buildcc::env::save_file(FILENAME, "Hello", false); + CHECK_FALSE(save); +} + +#endif + +TEST(EnvUtilTestGroup, Util_Split) { + { + std::vector paths = buildcc::env::split("", ':'); + CHECK_EQUAL(paths.size(), 0); + } + + { + std::vector paths = buildcc::env::split("path1", ':'); + CHECK_EQUAL(paths.size(), 1); + STRCMP_EQUAL(paths[0].c_str(), "path1"); + } + + { + std::vector paths = + buildcc::env::split("path1:path2:path3", ':'); + CHECK_EQUAL(paths.size(), 3); + STRCMP_EQUAL(paths[0].c_str(), "path1"); + STRCMP_EQUAL(paths[1].c_str(), "path2"); + STRCMP_EQUAL(paths[2].c_str(), "path3"); + } +} + +int main(int ac, char **av) { + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/env/test/test_static_project.cpp b/buildcc/lib/env/test/test_static_project.cpp new file mode 100644 index 00000000..b5b4a5aa --- /dev/null +++ b/buildcc/lib/env/test/test_static_project.cpp @@ -0,0 +1,28 @@ +#include "env/env.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" + +// clang-format off +TEST_GROUP(StaticProjectTestGroup) +{ + void setup() { + } +}; +// clang-format on + +TEST(StaticProjectTestGroup, ProjectInitialized) { + CHECK_FALSE(buildcc::Project::IsInit()); + buildcc::Project::Init(fs::current_path(), fs::current_path()); + CHECK_TRUE(buildcc::Project::IsInit()); + buildcc::Project::Deinit(); + CHECK_FALSE(buildcc::Project::IsInit()); +} + +int main(int ac, char **av) { + MemoryLeakWarningPlugin::turnOffNewDeleteOverloads(); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/env/test/test_storage.cpp b/buildcc/lib/env/test/test_storage.cpp new file mode 100644 index 00000000..4c38b134 --- /dev/null +++ b/buildcc/lib/env/test/test_storage.cpp @@ -0,0 +1,118 @@ +#include "env/storage.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(ScopedStorageTestGroup) +{ +}; + +TEST_GROUP(StorageTestGroup) +{ + void setup() { + MemoryLeakWarningPlugin::saveAndDisableNewDeleteOverloads(); + } + void teardown() { + buildcc::Storage::Clear(); + MemoryLeakWarningPlugin::restoreNewDeleteOverloads(); + } +}; +// clang-format on + +class MyScopedStorage : public buildcc::ScopedStorage { +public: + // We want to unit test this + template void Remove(T *ptr) { + this->ScopedStorage::Remove(ptr); + } +}; + +class BigObj {}; + +class BigObjWithParameters { +public: + BigObjWithParameters(const std::string &name, int id, const BigObj &obj) + : name_(name) { + (void)id; + (void)obj; + } + + const std::string &GetName() const { return name_; } + +private: + std::string name_; +}; + +static BigObj obj; + +TEST(ScopedStorageTestGroup, BasicUsage) { + MyScopedStorage storage; + storage.Add("identifier", "name", 10, obj); + storage.Add("identifier2", "name2", 12, obj); + + // Usage + storage.ConstRef("identifier").GetName(); + storage.Ref("identifier2").GetName(); + + CHECK_TRUE(storage.Contains("identifier")); + CHECK_FALSE(storage.Contains("identifier_does_not_exist")); + + CHECK_TRUE(storage.Valid("identifier")); + CHECK_FALSE(storage.Valid("wrong_identifier")); + CHECK_FALSE(storage.Valid("identifier")); + + storage.Clear(); + CHECK_FALSE(storage.Contains("identifier")); + + // Automatic cleanup here +} + +TEST(ScopedStorageTestGroup, IncorrectUsage) { + MyScopedStorage storage; + storage.Add("identifier", "name", 10, obj); + + // We try to cast to a different type! + CHECK_THROWS(std::exception, storage.Ref("identifier")); + + // We use a wrong identifier + CHECK_THROWS(std::exception, + storage.Ref("identifier2")); +} + +TEST(ScopedStorageTestGroup, NullptrDelete) { + MyScopedStorage storage; + storage.Remove(nullptr); +} + +// + +TEST(StorageTestGroup, BasicUsage) { + buildcc::Storage::Add("identifier", "name", 10, obj); + buildcc::Storage::Add("identifier2", "name2", 12, obj); + + // Usage + const auto &bigobj = + buildcc::Storage::ConstRef("identifier").GetName(); + const auto &bigobj2 = + buildcc::Storage::Ref("identifier2").GetName(); + + STRCMP_EQUAL(bigobj.c_str(), "name"); + STRCMP_EQUAL(bigobj2.c_str(), "name2"); + + CHECK_TRUE(buildcc::Storage::Contains("identifier")); + CHECK_FALSE(buildcc::Storage::Contains("identifier_does_not_exist")); + + CHECK_TRUE(buildcc::Storage::Valid("identifier")); + CHECK_FALSE( + buildcc::Storage::Valid("wrong_identifier")); + CHECK_FALSE(buildcc::Storage::Valid("identifier")); +} + +int main(int ac, char **av) { + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/env/test/test_task_state.cpp b/buildcc/lib/env/test/test_task_state.cpp new file mode 100644 index 00000000..0b02b1dc --- /dev/null +++ b/buildcc/lib/env/test/test_task_state.cpp @@ -0,0 +1,71 @@ +#include "env/task_state.h" + +#include "taskflow/taskflow.hpp" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" + +// clang-format off +TEST_GROUP(TaskStateTestGroup) +{ + void setup() { + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + } +}; +// clang-format on + +TEST(TaskStateTestGroup, OneTask) { + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + + tf::Taskflow tf; + bool completed = false; + tf.emplace([&]() { + buildcc::env::set_task_state(buildcc::env::TaskState::FAILURE); + completed = true; + }); + tf::Executor executor(2); + executor.run(tf); + executor.wait_for_all(); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + CHECK_TRUE(completed); +} + +TEST(TaskStateTestGroup, MultipleTasks) { + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + + tf::Taskflow tf; + bool completed1 = false; + tf.emplace([&]() { + buildcc::env::set_task_state(buildcc::env::TaskState::FAILURE); + completed1 = true; + }); + + bool completed2 = false; + tf.emplace([&]() { + buildcc::env::set_task_state(buildcc::env::TaskState::FAILURE); + completed2 = true; + }); + + bool completed3 = false; + tf.emplace([&]() { + buildcc::env::set_task_state(buildcc::env::TaskState::FAILURE); + completed3 = true; + }); + + tf::Executor executor(2); + executor.run(tf); + executor.wait_for_all(); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + CHECK_TRUE(completed1); + CHECK_TRUE(completed2); + CHECK_TRUE(completed3); +} + +int main(int ac, char **av) { + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/target/CMakeLists.txt b/buildcc/lib/target/CMakeLists.txt index a7aa69c9..e530fde4 100644 --- a/buildcc/lib/target/CMakeLists.txt +++ b/buildcc/lib/target/CMakeLists.txt @@ -1,18 +1,14 @@ # Target mocks and tests +include(cmake/common_target_src.cmake) + if (${TESTING}) set(TARGET_DIR "${CMAKE_CURRENT_SOURCE_DIR}") include(cmake/mock_target.cmake) - add_subdirectory(test/path) add_subdirectory(test/target) endif() -# Target flatc generator -include(cmake/fbs_gen.cmake) - # Target lib include(cmake/target.cmake) # Target install -if (${BUILDCC_INSTALL}) - include(cmake/target_install.cmake) -endif() +include(cmake/target_install.cmake) diff --git a/buildcc/lib/target/cmake/common_target_src.cmake b/buildcc/lib/target/cmake/common_target_src.cmake new file mode 100644 index 00000000..69558576 --- /dev/null +++ b/buildcc/lib/target/cmake/common_target_src.cmake @@ -0,0 +1,55 @@ +set(COMMON_TARGET_SRCS + # Interfaces + include/target/interface/builder_interface.h + + # Common + src/common/target_config.cpp + src/common/target_state.cpp + include/target/common/target_config.h + include/target/common/target_state.h + include/target/common/target_env.h + include/target/common/util.h + + # API + src/api/lib_api.cpp + include/target/api/source_api.h + include/target/api/include_api.h + include/target/api/lib_api.h + include/target/api/pch_api.h + include/target/api/deps_api.h + + src/api/sync_api.cpp + include/target/api/sync_api.h + + src/api/target_getter.cpp + include/target/api/target_getter.h + + # Generator + include/target/custom_generator/custom_generator_context.h + include/target/custom_generator/custom_blob_handler.h + + src/custom_generator/custom_generator.cpp + include/target/custom_generator.h + src/generator/file_generator.cpp + include/target/file_generator.h + src/generator/template_generator.cpp + include/target/template_generator.h + + # Target Info + src/target_info/target_info.cpp + include/target/target_info.h + + # Target friend + src/target/friend/compile_pch.cpp + src/target/friend/compile_object.cpp + src/target/friend/link_target.cpp + include/target/friend/compile_pch.h + include/target/friend/compile_object.h + include/target/friend/link_target.h + + # Target + src/target/target.cpp + src/target/build.cpp + src/target/tasks.cpp + include/target/target.h +) diff --git a/buildcc/lib/target/cmake/fbs_gen.cmake b/buildcc/lib/target/cmake/fbs_gen.cmake deleted file mode 100644 index addd0800..00000000 --- a/buildcc/lib/target/cmake/fbs_gen.cmake +++ /dev/null @@ -1,19 +0,0 @@ -# Generate files -set(FBS_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/fbs/target.fbs -) -set(FBS_GEN_FILES - ${CMAKE_CURRENT_BINARY_DIR}/generated/target_generated.h -) -set(FBS_GEN_OPTIONS - --gen-object-api -) - -add_custom_command(OUTPUT ${FBS_GEN_FILES} - COMMAND flatc -o ${CMAKE_CURRENT_BINARY_DIR}/generated ${FBS_GEN_OPTIONS} --cpp ${FBS_FILES} - DEPENDS flatc ${FBS_FILES} -) - -add_custom_target(fbs_to_header - DEPENDS ${FBS_GEN_FILES} -) diff --git a/buildcc/lib/target/cmake/mock_target.cmake b/buildcc/lib/target/cmake/mock_target.cmake index 3911def5..a648c983 100644 --- a/buildcc/lib/target/cmake/mock_target.cmake +++ b/buildcc/lib/target/cmake/mock_target.cmake @@ -1,39 +1,28 @@ -add_library(mock_target STATIC) +add_library(mock_target STATIC + ${COMMON_TARGET_SRCS} + # Custom Generator mocks + mock/custom_generator/runner.cpp + mock/custom_generator/recheck_states.cpp + + # Target mocks + mock/target/runner.cpp + mock/target/recheck_states.cpp +) target_include_directories(mock_target PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/mock - ${CMAKE_CURRENT_BINARY_DIR}/generated -) - -target_sources(mock_target PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src/target/target.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/target/source.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/target/include_dir.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/target/lib.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/target/build.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/target/flags.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/mock/target/recheck_states.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/mock/target/tasks.cpp - - ${CMAKE_CURRENT_SOURCE_DIR}/src/fbs/fbs_loader.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/fbs/fbs_storer.cpp - - ${CMAKE_CURRENT_SOURCE_DIR}/src/util/util.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/mock/util/command.cpp ) target_compile_options(mock_target PUBLIC ${TEST_COMPILE_FLAGS} ${BUILD_COMPILE_FLAGS}) target_link_options(mock_target PUBLIC ${TEST_LINK_FLAGS} ${BUILD_LINK_FLAGS}) target_link_libraries(mock_target PUBLIC - flatbuffers Taskflow - mock_env - toolchain + mock_toolchain CppUTest CppUTestExt - gcov + ${TEST_LINK_LIBS} ) # https://github.com/msys2/MINGW-packages/issues/2303 @@ -42,5 +31,3 @@ if (${MINGW}) message(WARNING "-Wl,--allow-multiple-definition for MINGW") target_link_options(mock_target PUBLIC -Wl,--allow-multiple-definition) endif() - -add_dependencies(mock_target fbs_to_header) diff --git a/buildcc/lib/target/cmake/target.cmake b/buildcc/lib/target/cmake/target.cmake index 7cc7d5d9..dc31284a 100644 --- a/buildcc/lib/target/cmake/target.cmake +++ b/buildcc/lib/target/cmake/target.cmake @@ -1,45 +1,34 @@ -# Target -m_clangtidy("target") -add_library(target) -target_include_directories(target PUBLIC - $ - $ -) -target_link_libraries(target PUBLIC - env - toolchain - flatbuffers - Taskflow -) -target_link_libraries(target PRIVATE - tiny-process-library::tiny-process-library -) +set(TARGET_SRCS + ${COMMON_TARGET_SRCS} -target_sources(target PRIVATE - src/target/target.cpp - src/target/source.cpp - src/target/include_dir.cpp - src/target/lib.cpp - src/target/build.cpp - src/target/flags.cpp + src/custom_generator/recheck_states.cpp src/target/recheck_states.cpp - src/target/tasks.cpp - - src/fbs/fbs_loader.cpp - src/fbs/fbs_storer.cpp +) - src/util/util.cpp - src/util/command.cpp +if(${BUILDCC_BUILD_AS_SINGLE_LIB}) + target_sources(buildcc PRIVATE + ${TARGET_SRCS} + ) + target_include_directories(buildcc PUBLIC + $ + $ + ) +endif() - include/target.h - include/internal/fbs_loader.h - include/internal/path.h - include/internal/util.h -) -target_include_directories(target PRIVATE - ${CMAKE_CURRENT_BINARY_DIR}/generated -) -target_compile_options(target PRIVATE ${BUILD_COMPILE_FLAGS}) -target_link_options(target PRIVATE ${BUILD_LINK_FLAGS}) -add_dependencies(target fbs_to_header) +if(${BUILDCC_BUILD_AS_INTERFACE}) + m_clangtidy("target") + add_library(target + ${TARGET_SRCS} + ) + target_include_directories(target PUBLIC + $ + $ + ) + target_link_libraries(target PUBLIC + toolchain + Taskflow + ) + target_compile_options(target PRIVATE ${BUILD_COMPILE_FLAGS}) + target_link_options(target PRIVATE ${BUILD_LINK_FLAGS}) +endif() diff --git a/buildcc/lib/target/cmake/target_install.cmake b/buildcc/lib/target/cmake/target_install.cmake index 85155947..64da84ae 100644 --- a/buildcc/lib/target/cmake/target_install.cmake +++ b/buildcc/lib/target/cmake/target_install.cmake @@ -1,12 +1,7 @@ -install(TARGETS target DESTINATION lib EXPORT targetConfig) -install(FILES - ${CMAKE_CURRENT_SOURCE_DIR}/include/target.h - DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}" -) -install(FILES - ${CMAKE_CURRENT_SOURCE_DIR}/include/internal/fbs_loader.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/internal/path.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/internal/util.h - DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}/internal" -) -install(EXPORT targetConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/target") +if (${BUILDCC_INSTALL}) + if(${BUILDCC_BUILD_AS_INTERFACE}) + install(TARGETS target DESTINATION lib EXPORT targetConfig) + install(EXPORT targetConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/target") + endif() + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}") +endif() diff --git a/buildcc/lib/target/fbs/target.fbs b/buildcc/lib/target/fbs/target.fbs deleted file mode 100644 index 2bbfa3fd..00000000 --- a/buildcc/lib/target/fbs/target.fbs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2021 Niket Naidu. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace schema.internal; - -// TODO, Custom executables -table Toolchain { - name:string (key); - asm_compiler:string; - c_compiler:string; - cpp_compiler:string; - archiver:string; - linker:string; -} - -table Path { - pathname:string (key); - last_write_timestamp:uint64; -} - -enum TargetType : byte { - Executable, - StaticLibrary, - DynamicLibrary -} - -table Target { - name:string (key); - relative_path:string; - type:TargetType; - toolchain:Toolchain; - - // Files - source_files:[Path]; - header_files:[Path]; - lib_deps:[Path]; - - // Links - external_lib_deps:[string]; - - // Directories - include_dirs:[string]; - lib_dirs:[string]; - - // Flags - preprocessor_flags:[string]; - c_compile_flags:[string]; - cpp_compile_flags:[string]; - link_flags:[string]; -} - -root_type Target; diff --git a/buildcc/lib/target/include/internal/fbs_loader.h b/buildcc/lib/target/include/internal/fbs_loader.h deleted file mode 100644 index 4a479087..00000000 --- a/buildcc/lib/target/include/internal/fbs_loader.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TARGET_INCLUDE_INTERNAL_FBS_LOADER_H_ -#define TARGET_INCLUDE_INTERNAL_FBS_LOADER_H_ - -#include -#include -#include - -#include "path.h" - -namespace fs = std::filesystem; - -namespace buildcc::internal { - -class FbsLoader { -public: - explicit FbsLoader(const std::string &name, const fs::path &relative_path) - : name_(name), relative_path_(relative_path) { - Initialize(); - } - - FbsLoader(const FbsLoader &loader) = delete; - -public: - bool Load(); - - // Getters - bool IsLoaded() const { return loaded_; } - fs::path GetBinaryPath() const { return relative_path_ / (name_ + ".bin"); } - - const path_unordered_set &GetLoadedSources() const { return loaded_sources_; } - const path_unordered_set &GetLoadedHeaders() const { return loaded_headers_; } - const path_unordered_set &GetLoadedLibDeps() const { - return loaded_lib_deps_; - } - const std::unordered_set &GetLoadedExternalLibDeps() const { - return loaded_external_lib_dirs_; - } - - const std::unordered_set &GetLoadedIncludeDirs() const { - return loaded_include_dirs_; - } - const std::unordered_set &GetLoadedLibDirs() const { - return loaded_lib_dirs_; - } - const std::unordered_set &GetLoadedPreprocessorFlags() const { - return loaded_preprocessor_flags_; - } - const std::unordered_set &GetLoadedCCompileFlags() const { - return loaded_c_compile_flags_; - } - const std::unordered_set &GetLoadedCppCompileFlags() const { - return loaded_cpp_compile_flags_; - } - const std::unordered_set &GetLoadedLinkFlags() const { - return loaded_link_flags_; - } - -private: - void Initialize(); - -private: - std::string name_; - fs::path relative_path_; - bool loaded_ = false; - - path_unordered_set loaded_sources_; - path_unordered_set loaded_headers_; - path_unordered_set loaded_lib_deps_; - - std::unordered_set loaded_external_lib_dirs_; - - std::unordered_set loaded_include_dirs_; - std::unordered_set loaded_lib_dirs_; - - std::unordered_set loaded_preprocessor_flags_; - std::unordered_set loaded_c_compile_flags_; - std::unordered_set loaded_cpp_compile_flags_; - std::unordered_set loaded_link_flags_; -}; - -} // namespace buildcc::internal - -#endif diff --git a/buildcc/lib/target/include/internal/path.h b/buildcc/lib/target/include/internal/path.h deleted file mode 100644 index ca734d78..00000000 --- a/buildcc/lib/target/include/internal/path.h +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TARGET_INCLUDE_INTERNAL_PATH_H_ -#define TARGET_INCLUDE_INTERNAL_PATH_H_ - -#include -#include - -// The Path class defined below is meant to be used with Sets -#include - -// Env -#include "assert_fatal.h" - -// Third party -#include "fmt/format.h" - -namespace fs = std::filesystem; - -namespace buildcc::internal { - -class Path { -public: - /** - * @brief Create a Existing Path object and sets last_write_timstamp to file - * timestamp - * NOTE, Throws buildcc::env::assert_exception if file not found - * - * @param pathname - * @return Path - */ - static Path CreateExistingPath(const fs::path &pathname) { - std::error_code errcode; - uint64_t last_write_timestamp = - std::filesystem::last_write_time(pathname, errcode) - .time_since_epoch() - .count(); - env::assert_fatal(errcode.value() == 0, - fmt::format("{} not found", pathname.string())); - - return Path(pathname, last_write_timestamp); - } - - static Path CreateNewPath(const fs::path &pathname, - uint64_t last_write_timestamp) noexcept { - return Path(pathname, last_write_timestamp); - } - - /** - * @brief Create a New Path object and sets last_write_timestamp to 0 - * - * @param pathname - * @return Path - */ - static Path CreateNewPath(const fs::path &pathname) noexcept { - return Path(pathname, 0); - } - - // Setters - void SetLastWriteTimestamp(std::uint64_t timestamp) { - last_write_timestamp_ = timestamp; - } - - // Getters - std::uint64_t GetLastWriteTimestamp() const { return last_write_timestamp_; } - const fs::path &GetPathname() const { return pathname_; } - std::string GetPathAsString() const { return quote(GetPathname().string()); } - - // Used during find operation - bool operator==(const Path &p) const { - return GetPathname() == p.GetPathname(); - } - - bool operator==(const fs::path &pathname) const { - return GetPathname() == pathname; - } - -private: - explicit Path(const fs::path &pathname, std::uint64_t last_write_timestamp) - : pathname_(pathname), last_write_timestamp_(last_write_timestamp) { - pathname_.make_preferred(); - } - - std::string quote(const std::string &str) const { - if (str.find(" ") == std::string::npos) { - return str; - } - return fmt::format("\"{}\"", str); - } - -private: - fs::path pathname_; - std::uint64_t last_write_timestamp_; -}; - -// Used by Path -class PathHash { -public: - size_t operator()(const Path &p) const { - return std::hash()(p.GetPathname().string()); - } -}; - -typedef std::unordered_set path_unordered_set; - -} // namespace buildcc::internal - -#endif diff --git a/buildcc/lib/target/include/internal/util.h b/buildcc/lib/target/include/internal/util.h deleted file mode 100644 index eb219b1c..00000000 --- a/buildcc/lib/target/include/internal/util.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TARGET_INCLUDE_INTERNAL_UTIL_H_ -#define TARGET_INCLUDE_INTERNAL_UTIL_H_ - -#include -#include - -#include "internal/path.h" - -namespace buildcc::internal { - -// System -/** - * @brief Executes an external command - * Internally command uses `std::system` - * TODO, Replace with `subprocess` - * - * @param command_tokens - * @return true - * @return false - */ -bool command(const std::vector &command_tokens); - -// Additions -/** - * @brief Existing path is stored inside stored_paths - * Returns false if path is stored - * Throws buildcc::env::assert_exception if path does not exist - * - * @param path - * @param stored_paths - * @return true - * @return false - */ -bool add_path(const fs::path &path, path_unordered_set &stored_paths); - -// Checks -/** - * @brief Perform check to see if previous path is present in current path - * - * @param previous_paths - * @param current_paths - * @return true - * @return false - */ -bool is_previous_paths_different(const path_unordered_set &previous_paths, - const path_unordered_set ¤t_paths); - -// Aggregates -std::string aggregate(const std::vector &list); -std::string aggregate(const std::unordered_set &list); -std::string aggregate(const buildcc::internal::path_unordered_set &paths); - -std::string aggregate_with_prefix(const std::string &prefix, - const std::unordered_set &dirs); - -} // namespace buildcc::internal - -#endif diff --git a/buildcc/lib/target/include/target.h b/buildcc/lib/target/include/target.h deleted file mode 100644 index d06deb53..00000000 --- a/buildcc/lib/target/include/target.h +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TARGET_INCLUDE_TARGET_H_ -#define TARGET_INCLUDE_TARGET_H_ - -#include -#include -#include -#include -#include -#include - -// Internal -#include "internal/fbs_loader.h" -#include "internal/path.h" - -// Buildcc -#include "toolchain.h" - -// Env -#include "env.h" - -// Third Party -#include "taskflow/taskflow.hpp" - -namespace buildcc::base { - -namespace fs = std::filesystem; - -enum class FileExtType { - Asm, - C, - Cpp, - Header, - Invalid, -}; - -enum class TargetType { - Executable, - StaticLibrary, - DynamicLibrary, -}; - -class Target { -public: - explicit Target(const std::string &name, TargetType type, - const Toolchain &toolchain, - const fs::path &target_path_relative_to_root) - : name_(name), type_(type), toolchain_(toolchain), - target_root_source_dir_(env::get_project_root_dir() / - target_path_relative_to_root), - target_intermediate_dir_(fs::path(env::get_project_build_dir()) / - toolchain.GetName() / name), - loader_(name, target_intermediate_dir_) { - Initialize(); - } - - Target(const Target &target) = delete; - - // Builders - void Build(); - - // Setters - - // * Sources - void AddSource(const fs::path &relative_filename); - void AddSource(const fs::path &relative_filename, - const fs::path &relative_to_target_path); - void GlobSources(const fs::path &relative_to_target_path); - - // Use these APIs for out of project root builds - // Manually specify input and output - void AddSourceAbsolute(const fs::path &absolute_input_filepath, - const fs::path &absolute_output_filepath); - void GlobSourcesAbsolute(const fs::path &absolute_input_path, - const fs::path &absolute_output_path); - - // * Headers - void AddHeader(const std::string &relative_filename); - void AddHeader(const std::string &relative_filename, - const fs::path &relative_to_target_path); - void AddHeaderAbsolute(const fs::path &absolute_filepath); - - void GlobHeaders(const fs::path &relative_to_target_path); - void GlobHeadersAbsolute(const fs::path &absolute_path); - - // * Include and Lib directory - void AddIncludeDir(const fs::path &relative_include_dir, - bool glob_headers = false); - void AddIncludeDirAbsolute(const fs::path &absolute_include_dir, - bool glob_headers = false); - - void AddLibDir(const fs::path &relative_lib_dir); - void AddLibDirAbsolute(const fs::path &absolute_lib_dir); - - // * Libraries - void AddLibDep(const Target &lib_dep); - void AddLibDep(const std::string &lib_dep); - - // * Flags - void AddPreprocessorFlag(const std::string &flag); - void AddCCompileFlag(const std::string &flag); - void AddCppCompileFlag(const std::string &flag); - void AddLinkFlag(const std::string &flag); - - // TODO, Add more setters - - // Getters - std::vector CompileCommand(const fs::path ¤t_source) const; - - // TODO, Check if these need to be made const - tf::Taskflow &GetTaskflow() { return tf_; } - tf::Task &GetCompileTask() { return compile_task_; } - tf::Task &GetLinkTask() { return link_task_; } - - fs::path GetTargetPath() const { - fs::path path = GetTargetIntermediateDir() / GetName(); - path.make_preferred(); - return path; - } - fs::path GetBinaryPath() const { return loader_.GetBinaryPath(); } - - // Const references - const std::string &GetName() const { return name_; } - const Toolchain &GetToolchain() const { return toolchain_; } - base::TargetType GetTargetType() const { return type_; } - - const fs::path &GetTargetRootDir() const { return target_root_source_dir_; } - const fs::path &GetTargetIntermediateDir() const { - return target_intermediate_dir_; - } - - // TODO, Consider returning `std::vector` OR - // `std::vector` for these getters - // These APIs are meant to be consumed by users - const internal::path_unordered_set &GetCurrentSourceFiles() const { - return current_source_files_; - } - const internal::path_unordered_set &GetCurrentHeaderFiles() const { - return current_header_files_; - } - - bool FirstBuild() const { return first_build_; } - bool Rebuild() const { return rebuild_; } - - // TODO, Add more getters - -public: - std::string prefix_include_dir_{"-I"}; - std::string prefix_lib_dir_{"-L"}; - std::unordered_set valid_c_ext_{".c"}; - std::unordered_set valid_cpp_ext_{".cpp", ".cxx", ".cc"}; - std::unordered_set valid_asm_ext_{".s", ".S", ".asm"}; - std::unordered_set valid_header_ext_{".h", ".hpp"}; - -protected: - // Getters - FileExtType GetFileExtType(const fs::path &filepath) const; - bool IsValidSource(const fs::path &sourcepath) const; - bool IsValidHeader(const fs::path &headerpath) const; - - const std::string &GetCompiler(const fs::path &source) const; - - const internal::Path &GetCompiledSourcePath(const fs::path &source) const; - internal::path_unordered_set GetCompiledSources() const; - -private: - void Initialize(); - - // Build - void BuildCompile(); - void BuildRecompile(); - - // Compile - void CompileSources(); - void RecompileSources(); - - void CompileSource(const fs::path ¤t_source) const; - - // * Virtual - // PreCompile(); - // Compile(); - // PostCompile(); - virtual std::vector - CompileCommand(const std::string &input_source, - const std::string &output_source, const std::string &compiler, - const std::string &aggregated_preprocessor_flags, - const std::string &aggregated_compile_flags, - const std::string &aggregated_include_dirs) const; - - // Recompilation checks - void RecheckPaths(const internal::path_unordered_set &previous_path, - const internal::path_unordered_set ¤t_path); - - void RecheckDirs(const std::unordered_set &previous_dirs, - const std::unordered_set ¤t_dirs); - void RecheckFlags(const std::unordered_set &previous_flags, - const std::unordered_set ¤t_flags); - void RecheckExternalLib( - const std::unordered_set &previous_external_libs, - const std::unordered_set ¤t_external_libs); - - // Helper function - void RecheckChanged(const std::unordered_set &previous, - const std::unordered_set ¤t, - std::function callback); - - // Tasks - void CompileTargetTask(const std::vector &&compile_sources, - const std::vector &&dummy_compile_sources); - - void LinkTargetTask(const bool link); - - // * Virtual - // PreLink(); - // Link(); - // PostLink(); - - void LinkTarget(); - - // TODO, Add Link library paths - virtual std::vector - Link(const std::string &output_target, - const std::string &aggregated_link_flags, - const std::string &aggregated_compiled_sources, - const std::string &aggregated_lib_dirs, - const std::string &aggregated_lib_deps) const; - - // Fbs - bool Store(); - - // Callbacks - void SourceRemoved(); - void SourceAdded(); - void SourceUpdated(); - void PathRemoved(); - void PathAdded(); - void PathUpdated(); - - void DirChanged(); - void FlagChanged(); - void ExternalLibChanged(); - -private: - // Constructor defined - std::string name_; - TargetType type_; - const Toolchain &toolchain_; - fs::path target_root_source_dir_; - fs::path target_intermediate_dir_; - - // Internal - internal::path_unordered_set current_source_files_; - // NOTE, Always store the absolute source path -> absolute compiled source - // path here - std::unordered_map - current_object_files_; - - internal::path_unordered_set current_header_files_; - internal::path_unordered_set current_lib_deps_; - - // TODO, Change these to PATHS - std::unordered_set current_include_dirs_; - std::unordered_set current_lib_dirs_; - - std::unordered_set current_external_lib_deps_; - - std::unordered_set current_preprocessor_flags_; - std::unordered_set current_c_compile_flags_; - std::unordered_set current_cpp_compile_flags_; - std::unordered_set current_link_flags_; - - // TODO, Make appending to this more efficient - // TODO, Might not need to be persistent - // TODO, aggregates might not need to exist, check `std::insert` APIs over - // vector and unordered_set - std::string aggregated_include_dirs_; - std::string aggregated_lib_dirs_; - // NOTE, This contains current_external_lib_deps_ + current_lib_deps_ - std::string aggregated_lib_deps_; - - std::string aggregated_preprocessor_flags_; - std::string aggregated_c_compile_flags_; - std::string aggregated_cpp_compile_flags_; - std::string aggregated_link_flags_; - - // TODO, Add more internal variables - - internal::FbsLoader loader_; - bool dirty_ = false; - - // Build states - bool first_build_ = false; - bool rebuild_ = false; - - static constexpr const char *const kCompileTaskName = "Compile"; - static constexpr const char *const kLinkTaskName = "Link"; - - tf::Taskflow tf_; - tf::Task compile_task_; - tf::Task link_task_; -}; - -} // namespace buildcc::base - -#endif diff --git a/buildcc/lib/target/include/target/api/deps_api.h b/buildcc/lib/target/include/target/api/deps_api.h new file mode 100644 index 00000000..dfb5bb99 --- /dev/null +++ b/buildcc/lib/target/include/target/api/deps_api.h @@ -0,0 +1,93 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_API_DEPS_API_H_ +#define TARGET_API_DEPS_API_H_ + +#include + +#include "schema/path.h" + +namespace fs = std::filesystem; + +namespace buildcc::internal { + +// Requires +// User::CompileDependencies +// User::LinkDependencies +// TargetEnv +template class DepsApi { +public: + // TODO, AddPchDependency + // TODO, Rename AddObjectDependency + // TODO, Rename AddTargetDependency + + std::vector GetCompileDependencies() const { + const auto &t = static_cast(*this); + return t.user_.compile_dependencies.GetPaths(); + } + + std::vector GetLinkDependencies() const { + const auto &t = static_cast(*this); + return t.user_.link_dependencies.GetPaths(); + } + + /** + * @brief Recompile sources to object if compile dependency is removed, added + * or newer from the previous build + */ + void AddCompileDependencyAbsolute(const fs::path &absolute_path) { + auto &t = static_cast(*this); + + t.user_.compile_dependencies.Emplace(absolute_path, ""); + } + + /** + * @brief Recompile sources to object if compile dependency is removed, added + * or newer from the previous build + */ + void AddCompileDependency(const fs::path &relative_path) { + auto &t = static_cast(*this); + + fs::path absolute_path = t.env_.GetTargetRootDir() / relative_path; + AddCompileDependencyAbsolute(absolute_path); + } + + /** + * @brief Relink target if link dependency is removed, added or newer from + * previous build + */ + void AddLinkDependencyAbsolute(const fs::path &absolute_path) { + auto &t = static_cast(*this); + + t.user_.link_dependencies.Emplace(absolute_path, ""); + } + + /** + * @brief Relink target if link dependency is removed, added or newer from + * previous build + */ + void AddLinkDependency(const fs::path &relative_path) { + auto &t = static_cast(*this); + + fs::path absolute_path = t.env_.GetTargetRootDir() / relative_path; + AddLinkDependencyAbsolute(absolute_path); + } +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/lib/target/include/target/api/include_api.h b/buildcc/lib/target/include/target/api/include_api.h new file mode 100644 index 00000000..d6702dc1 --- /dev/null +++ b/buildcc/lib/target/include/target/api/include_api.h @@ -0,0 +1,103 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_API_INCLUDE_API_H_ +#define TARGET_API_INCLUDE_API_H_ + +#include + +#include "schema/path.h" + +namespace fs = std::filesystem; + +namespace buildcc::internal { + +// Requires +// Toolchain +// User::Headers +// User::IncludeDirs +// TargetEnv +template class IncludeApi { +public: + std::vector GetHeaderFiles() const { + const auto &t = static_cast(*this); + return t.user_.headers.GetPaths(); + } + + const std::vector &GetIncludeDirs() const { + const auto &t = static_cast(*this); + return t.user_.include_dirs.GetPaths(); + } + + void AddHeaderAbsolute(const fs::path &absolute_filepath) { + auto &t = static_cast(*this); + + t.toolchain_.GetConfig().ExpectsValidHeader(absolute_filepath); + t.user_.headers.Emplace(absolute_filepath, ""); + } + + void GlobHeadersAbsolute(const fs::path &absolute_path) { + auto &t = static_cast(*this); + + for (const auto &p : fs::directory_iterator(absolute_path)) { + if (t.toolchain_.GetConfig().IsValidHeader(p.path())) { + AddHeaderAbsolute(p.path()); + } + } + } + + void AddIncludeDirAbsolute(const fs::path &absolute_include_dir, + bool glob_headers = false) { + auto &t = static_cast(*this); + + t.user_.include_dirs.Emplace(absolute_include_dir); + + if (glob_headers) { + GlobHeadersAbsolute(absolute_include_dir); + } + } + + void AddHeader(const fs::path &relative_filename, + const fs::path &relative_to_target_path = "") { + auto &t = static_cast(*this); + + // Check Source + fs::path absolute_filepath = + t.env_.GetTargetRootDir() / relative_to_target_path / relative_filename; + AddHeaderAbsolute(absolute_filepath); + } + + void GlobHeaders(const fs::path &relative_to_target_path = "") { + auto &t = static_cast(*this); + + fs::path absolute_path = + t.env_.GetTargetRootDir() / relative_to_target_path; + GlobHeadersAbsolute(absolute_path); + } + + void AddIncludeDir(const fs::path &relative_include_dir, + bool glob_headers = false) { + auto &t = static_cast(*this); + + const fs::path absolute_include_dir = + t.env_.GetTargetRootDir() / relative_include_dir; + AddIncludeDirAbsolute(absolute_include_dir, glob_headers); + } +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/lib/target/include/target/api/lib_api.h b/buildcc/lib/target/include/target/api/lib_api.h new file mode 100644 index 00000000..5506bdf1 --- /dev/null +++ b/buildcc/lib/target/include/target/api/lib_api.h @@ -0,0 +1,82 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_API_LIB_API_H_ +#define TARGET_API_LIB_API_H_ + +#include +#include +#include + +#include "schema/path.h" + +namespace fs = std::filesystem; + +namespace buildcc { + +class Target; + +} + +namespace buildcc::internal { + +// Requires +// User::LibDirs +// User::Libs +// User::ExternalLibs +// TargetEnv +// Target::GetTargetPath +template class LibApi { +public: + std::vector GetLibDeps() const { + const auto &t = static_cast(*this); + return t.user_.libs.GetPaths(); + } + + const std::vector &GetExternalLibDeps() const { + const auto &t = static_cast(*this); + return t.user_.external_libs; + } + + const std::vector &GetLibDirs() const { + const auto &t = static_cast(*this); + return t.user_.lib_dirs.GetPaths(); + } + + void AddLibDirAbsolute(const fs::path &absolute_lib_dir) { + auto &t = static_cast(*this); + t.user_.lib_dirs.Emplace(absolute_lib_dir); + } + + void AddLibDir(const fs::path &relative_lib_dir) { + auto &t = static_cast(*this); + fs::path final_lib_dir = t.env_.GetTargetRootDir() / relative_lib_dir; + AddLibDirAbsolute(final_lib_dir); + } + + void AddLibDep(const std::string &lib_dep) { + auto &t = static_cast(*this); + t.user_.external_libs.push_back(lib_dep); + } + + // Target class has been forward declared + // This is because this file is meant to be used by `TargetInfo` and `Target` + void AddLibDep(const Target &lib_dep); +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/lib/target/include/target/api/pch_api.h b/buildcc/lib/target/include/target/api/pch_api.h new file mode 100644 index 00000000..dec92f8d --- /dev/null +++ b/buildcc/lib/target/include/target/api/pch_api.h @@ -0,0 +1,62 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_API_PCH_API_H_ +#define TARGET_API_PCH_API_H_ + +#include + +#include "schema/path.h" + +namespace fs = std::filesystem; + +namespace buildcc::internal { + +// Requires +// Toolchain +// User::Pchs +// TargetEnv +template class PchApi { +public: + std::vector GetPchFiles() const { + const auto &t = static_cast(*this); + return t.user_.pchs.GetPaths(); + } + + void AddPchAbsolute(const fs::path &absolute_filepath) { + auto &t = static_cast(*this); + + t.toolchain_.GetConfig().ExpectsValidHeader(absolute_filepath); + + const fs::path absolute_pch = fs::path(absolute_filepath).make_preferred(); + t.user_.pchs.Emplace(absolute_pch, ""); + } + + void AddPch(const fs::path &relative_filename, + const fs::path &relative_to_target_path = "") { + auto &t = static_cast(*this); + + // Compute the absolute source path + fs::path absolute_pch = + t.env_.GetTargetRootDir() / relative_to_target_path / relative_filename; + + AddPchAbsolute(absolute_pch); + } +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/lib/target/include/target/api/source_api.h b/buildcc/lib/target/include/target/api/source_api.h new file mode 100644 index 00000000..d3674158 --- /dev/null +++ b/buildcc/lib/target/include/target/api/source_api.h @@ -0,0 +1,81 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_API_SOURCE_API_H_ +#define TARGET_API_SOURCE_API_H_ + +#include + +#include "schema/path.h" + +namespace fs = std::filesystem; + +namespace buildcc::internal { + +// Requires +// Toolchain +// User::Sources +// TargetEnv +template class SourceApi { +public: + std::vector GetSourceFiles() const { + const auto &t = static_cast(*this); + return t.user_.sources.GetPaths(); + } + + void AddSourceAbsolute(const fs::path &absolute_source) { + auto &t = static_cast(*this); + + t.toolchain_.GetConfig().ExpectsValidSource(absolute_source); + t.user_.sources.Emplace(absolute_source, ""); + } + + void GlobSourcesAbsolute(const fs::path &absolute_source_dir) { + auto &t = static_cast(*this); + + for (const auto &p : fs::directory_iterator(absolute_source_dir)) { + if (t.toolchain_.GetConfig().IsValidSource(p.path())) { + AddSourceAbsolute(p.path()); + } + } + } + + void AddSource(const fs::path &relative_source, + const fs::path &relative_to_target_path = "") { + auto &t = static_cast(*this); + + // Compute the absolute source path + fs::path absolute_source = + t.env_.GetTargetRootDir() / relative_to_target_path / relative_source; + AddSourceAbsolute(absolute_source); + } + + void GlobSources(const fs::path &relative_to_target_path = "") { + auto &t = static_cast(*this); + + fs::path absolute_input_path = + t.env_.GetTargetRootDir() / relative_to_target_path; + for (const auto &p : fs::directory_iterator(absolute_input_path)) { + if (t.toolchain_.GetConfig().IsValidSource(p.path())) { + AddSourceAbsolute(p.path()); + } + } + } +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/lib/target/include/target/api/sync_api.h b/buildcc/lib/target/include/target/api/sync_api.h new file mode 100644 index 00000000..c374caab --- /dev/null +++ b/buildcc/lib/target/include/target/api/sync_api.h @@ -0,0 +1,87 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_API_SYNC_API_H_ +#define TARGET_API_SYNC_API_H_ + +#include + +namespace buildcc::internal { + +enum class SyncOption { + SourceFiles, + HeaderFiles, + PchFiles, + LibDeps, + IncludeDirs, + LibDirs, + ExternalLibDeps, + PreprocessorFlags, + CommonCompileFlags, + PchCompileFlags, + PchObjectFlags, + AsmCompileFlags, + CCompileFlags, + CppCompileFlags, + LinkFlags, + CompileDependencies, + LinkDependencies, +}; + +// Requires +// - TargetStorer +template class SyncApi { +public: + /** + * @brief Copy/Replace selected variables when Target supplied by const + * reference + */ + void Copy(const T &target, std::initializer_list options); + + /** + * @brief Copy/Replace selected variables when Target supplied by move + */ + void Copy(T &&target, std::initializer_list options); + + /** + * @brief Insert selected variables when Target supplied by const reference + */ + void Insert(const T &target, std::initializer_list options); + + /** + * @brief Insert selected variables when Target supplied by move + * + */ + void Insert(T &&target, std::initializer_list options); + +private: + template + void SpecializedCopy(TargetType target, + std::initializer_list options); + template + void SpecializedInsert(TargetType target, + std::initializer_list options); +}; + +} // namespace buildcc::internal + +namespace buildcc { + +typedef internal::SyncOption SyncOption; + +} + +#endif diff --git a/buildcc/lib/target/include/target/api/target_getter.h b/buildcc/lib/target/include/target/api/target_getter.h new file mode 100644 index 00000000..7a05a79f --- /dev/null +++ b/buildcc/lib/target/include/target/api/target_getter.h @@ -0,0 +1,81 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_API_TARGET_GETTER_H_ +#define TARGET_API_TARGET_GETTER_H_ + +#include +#include + +#include "schema/target_type.h" + +#include "toolchain/toolchain.h" + +#include "target/common/target_config.h" +#include "target/common/target_state.h" + +#include "taskflow/taskflow.hpp" + +namespace fs = std::filesystem; + +namespace buildcc::internal { + +template class TargetGetter { +public: + // Target State + const TargetState &GetState() const; + bool IsBuilt() const; + + // Target Config + const TargetConfig &GetConfig() const; + + const std::string &GetName() const; + const Toolchain &GetToolchain() const; + TargetType GetType() const; + + /** + * @brief Location of generated Target + */ + const fs::path &GetTargetPath() const; + + /** + * @brief Location of serialized Target data + */ + const fs::path &GetBinaryPath() const; + + /** + * @brief BuildCC constructed PCH header file + * Example: + * - {file}.h + */ + const fs::path &GetPchHeaderPath() const; + + /** + * @brief PCH compiled file + * Example: + * - {file}.gch for GCC + * - {file}.pch for MSVC + */ + const fs::path &GetPchCompilePath() const; + + // TODO, Add GetPchCommand if required + const std::string &GetCompileCommand(const fs::path &source) const; + const std::string &GetLinkCommand() const; +}; + +}; // namespace buildcc::internal + +#endif diff --git a/buildcc/lib/target/include/target/common/target_config.h b/buildcc/lib/target/include/target/common/target_config.h new file mode 100644 index 00000000..a09d2595 --- /dev/null +++ b/buildcc/lib/target/include/target/common/target_config.h @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_COMMON_TARGET_CONFIG_H_ +#define TARGET_COMMON_TARGET_CONFIG_H_ + +#include +#include +#include + +namespace fs = std::filesystem; + +namespace buildcc { + +struct TargetConfig { + TargetConfig() = default; + + std::string target_ext{""}; + + // clang-format off + std::string pch_command{"{compiler} {preprocessor_flags} {include_dirs} {common_compile_flags} {pch_compile_flags} {compile_flags} -o {output} -c {input}"}; + std::string compile_command{"{compiler} {preprocessor_flags} {include_dirs} {common_compile_flags} {pch_object_flags} {compile_flags} -o {output} -c {input}"}; + std::string link_command{"{cpp_compiler} {link_flags} {compiled_sources} -o {output} {lib_dirs} {lib_deps}"}; + // clang-format on +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/target/include/target/common/target_env.h b/buildcc/lib/target/include/target/common/target_env.h new file mode 100644 index 00000000..dacdb847 --- /dev/null +++ b/buildcc/lib/target/include/target/common/target_env.h @@ -0,0 +1,106 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_COMMON_TARGET_ENV_H_ +#define TARGET_COMMON_TARGET_ENV_H_ + +#include + +#include "env/env.h" + +namespace fs = std::filesystem; + +namespace buildcc { + +class TargetEnv { +public: + // * NOTE, This has only been added for implicit conversion + // TODO, Make the constructors below explicit + // TODO, Remove this constructor + + /** + * @brief Change the relative root path for a particular Generator / Target + * + * Absolute root now changes to + * `Project::GetRootDir() / target_relative_to_env_root` + * + * Absolute build dir remains the same. + * + * Can be used implicitly + * + * @param target_relative_to_env_root Change root dir with respect to + * Project::GetRootDir() + */ + TargetEnv(const char *target_relative_to_env_root) + : TargetEnv(fs::path(target_relative_to_env_root)) {} + + /** + * @brief Similar to `TargetEnv(const char *)` + * + * Only explicit usage allowed + * + * @param target_relative_to_env_root Change root dir with respect to + * Project::GetRootDir() + */ + explicit TargetEnv(const fs::path &target_relative_to_env_root) + : target_root_dir_(Project::GetRootDir() / target_relative_to_env_root), + target_build_dir_(Project::GetBuildDir()), relative_(true) {} + + /** + * @brief Change the absolute root and build path for a particular Generator / + * Target + * + * @param absolute_target_root Absolute root directory for this target changes + * as per this parameter + * @param absolute_target_build Absolute build directory for this target + * changes as per this parameter + */ + explicit TargetEnv(const fs::path &absolute_target_root, + const fs::path &absolute_target_build) + : target_root_dir_(absolute_target_root), + target_build_dir_(absolute_target_build), relative_(false) {} + + const fs::path &GetTargetRootDir() const { return target_root_dir_; } + const fs::path &GetTargetBuildDir() const { return target_build_dir_; } + +private: + fs::path target_root_dir_; + fs::path target_build_dir_; + bool relative_{false}; +}; + +namespace internal { + +// Requires +// TargetEnv +template class TargetEnvApi { +public: + const fs::path &GetTargetRootDir() const { + const auto &t = static_cast(*this); + return t.env_.GetTargetRootDir(); + } + + const fs::path &GetTargetBuildDir() const { + const auto &t = static_cast(*this); + return t.env_.GetTargetBuildDir(); + } +}; + +} // namespace internal + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/target/include/target/common/target_state.h b/buildcc/lib/target/include/target/common/target_state.h new file mode 100644 index 00000000..bd211456 --- /dev/null +++ b/buildcc/lib/target/include/target/common/target_state.h @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_COMMON_TARGET_STATE_H_ +#define TARGET_COMMON_TARGET_STATE_H_ + +#include "toolchain/common/file_ext.h" + +namespace buildcc { + +struct TargetState { + void BuildCompleted(); + void SourceDetected(FileExt file_extension); + void PchDetected(); + + bool IsBuilt() const { return build_; } + bool ContainsPch() const { return contains_pch_; } + bool ContainsAsm() const { return contains_asm_; } + bool ContainsC() const { return contains_c_; } + bool ContainsCpp() const { return contains_cpp_; } + +private: + bool build_{false}; + + bool contains_pch_{false}; + bool contains_asm_{false}; + bool contains_c_{false}; + bool contains_cpp_{false}; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/target/include/target/common/util.h b/buildcc/lib/target/include/target/common/util.h new file mode 100644 index 00000000..30191e89 --- /dev/null +++ b/buildcc/lib/target/include/target/common/util.h @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_COMMON_UTIL_H_ +#define TARGET_COMMON_UTIL_H_ + +#include +#include + +#include "schema/path.h" + +namespace buildcc::internal { + +// Aggregates +template std::string aggregate(const T &list) { + return fmt::format("{}", fmt::join(list, " ")); +} + +template +std::string aggregate_with_prefix(const std::string &prefix, const T &list) { + std::vector agg_list; + for (const auto &l : list) { + auto formatted_output = fmt::format("{}{}", prefix, l); + agg_list.emplace_back(std::move(formatted_output)); + } + return aggregate(agg_list); +} + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/lib/target/include/target/custom_generator.h b/buildcc/lib/target/include/target/custom_generator.h new file mode 100644 index 00000000..a9dec033 --- /dev/null +++ b/buildcc/lib/target/include/target/custom_generator.h @@ -0,0 +1,141 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_CUSTOM_GENERATOR_H_ +#define TARGET_CUSTOM_GENERATOR_H_ + +#include +#include +#include +#include + +#include "env/command.h" +#include "env/task_state.h" + +#include "target/interface/builder_interface.h" + +#include "schema/custom_generator_serialization.h" +#include "schema/path.h" + +#include "custom_generator/custom_blob_handler.h" +#include "custom_generator/custom_generator_context.h" + +#include "target/common/target_env.h" + +namespace buildcc { + +struct UserCustomGeneratorSchema : public internal::CustomGeneratorSchema { + struct UserIdInfo : internal::CustomGeneratorSchema::IdInfo { + void ConvertToInternal() { + inputs.ComputeHashForAll(); + userblob = blob_handler != nullptr ? blob_handler->GetSerializedData() + : std::vector(); + } + + GenerateCb generate_cb; + std::shared_ptr blob_handler{nullptr}; + }; + + void ConvertToInternal() { + for (auto &[id_key, id_info] : ids) { + id_info.ConvertToInternal(); + auto [_, success] = internal_ids.try_emplace(id_key, id_info); + env::assert_fatal(success, fmt::format("Could not save {}", id_key)); + } + } + + std::unordered_map ids; +}; + +class CustomGenerator : public internal::BuilderInterface { +public: + CustomGenerator(const std::string &name, const TargetEnv &env) + : name_(name), + env_(env.GetTargetRootDir(), env.GetTargetBuildDir() / name), + serialization_(env_.GetTargetBuildDir() / + fmt::format("{}.json", name)) { + Initialize(); + } + virtual ~CustomGenerator() = default; + CustomGenerator(const CustomGenerator &) = delete; + + // TODO, Doc + void AddPattern(const std::string &identifier, const std::string &pattern); + + // TODO, Doc + void + AddPatterns(const std::unordered_map &pattern_map); + + // TODO, Doc + std::string ParsePattern(const std::string &pattern, + const std::unordered_map + &arguments = {}) const; + + /** + * @brief Single Generator task for inputs->generate_cb->outputs + * + * @param id Unique id associated with Generator task + * @param inputs File inputs + * @param outputs File outputs + * @param generate_cb User-defined generate callback to build outputs from the + * provided inputs + */ + void + AddIdInfo(const std::string &id, + const std::unordered_set &inputs, + const std::unordered_set &outputs, + const GenerateCb &generate_cb, + const std::shared_ptr &blob_handler = nullptr); + + void Build() override; + + // Getters + const std::string &GetName() const { return name_; } + const fs::path &GetBinaryPath() const { + return serialization_.GetSerializedFile(); + } + const fs::path &GetRootDir() const { return env_.GetTargetRootDir(); } + const fs::path &GetBuildDir() const { return env_.GetTargetBuildDir(); } + const std::string &Get(const std::string &file_identifier) const; + +private: + void Initialize(); + void GenerateTask(); + + // Recheck states + void IdRemoved(); + void IdAdded(); + void IdUpdated(); + +protected: + const env::Command &ConstCommand() const { return command_; } + env::Command &RefCommand() { return command_; } + +private: + std::string name_; + TargetEnv env_; + internal::CustomGeneratorSerialization serialization_; + + // Serialization + UserCustomGeneratorSchema user_; + + // Internal + env::Command command_; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/target/include/target/custom_generator/custom_blob_handler.h b/buildcc/lib/target/include/target/custom_generator/custom_blob_handler.h new file mode 100644 index 00000000..3b64d23c --- /dev/null +++ b/buildcc/lib/target/include/target/custom_generator/custom_blob_handler.h @@ -0,0 +1,107 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_CUSTOM_GENERATOR_CUSTOM_BLOB_HANDLER_H_ +#define TARGET_CUSTOM_GENERATOR_CUSTOM_BLOB_HANDLER_H_ + +#include + +#include "env/assert_fatal.h" + +namespace buildcc { + +/** + * @brief Abstract class for serializing additional data for which rebuilds + * might be triggered i.e data that is not input/output files + * TODO, Add examples here + * + */ +class CustomBlobHandler { +public: + CustomBlobHandler() = default; + virtual ~CustomBlobHandler() = default; + + bool CheckChanged(const std::vector &previous, + const std::vector ¤t) const { + env::assert_fatal( + Verify(previous), + "Stored blob is corrupted or User verification is incorrect"); + env::assert_fatal( + Verify(current), + "Current blob is corrupted or User verification is incorrect"); + return !IsEqual(previous, current); + }; + + std::vector GetSerializedData() const { + auto serialized_data = Serialize(); + env::assert_fatal( + Verify(serialized_data), + "Serialized data is corrupted or Serialize function is incorrect"); + return serialized_data; + } + +private: + virtual bool Verify(const std::vector &serialized_data) const = 0; + virtual bool IsEqual(const std::vector &previous, + const std::vector ¤t) const = 0; + virtual std::vector Serialize() const = 0; +}; + +/** + * @brief Typed Custom Blob handler which automatically performs Serialization + * and Deserialization as long as it is JSON serializable + * + * NOTE: Type data is stored as a reference (to avoid copying large amount of + * data) when constructing TypedCustomBlobHandler + * + * @tparam Type should be JSON serializable (see nlohmann::json compatible + * objects) + */ +template +class TypedCustomBlobHandler : public CustomBlobHandler { +public: + explicit TypedCustomBlobHandler(const Type &data) : data_(data) {} + + // serialized_data has already been verified + static Type Deserialize(const std::vector &serialized_data) { + json j = json::from_msgpack(serialized_data, true, false); + Type deserialized; + j.get_to(deserialized); + return deserialized; + } + +private: + const Type &data_; + + bool Verify(const std::vector &serialized_data) const override { + json j = json::from_msgpack(serialized_data, true, false); + return !j.is_discarded(); + } + + bool IsEqual(const std::vector &previous, + const std::vector ¤t) const override { + return Deserialize(previous) == Deserialize(current); + } + + std::vector Serialize() const override { + json j = data_; + return json::to_msgpack(j); + } +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/target/include/target/custom_generator/custom_generator_context.h b/buildcc/lib/target/include/target/custom_generator/custom_generator_context.h new file mode 100644 index 00000000..53bd59a6 --- /dev/null +++ b/buildcc/lib/target/include/target/custom_generator/custom_generator_context.h @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_CUSTOM_GENERATOR_CUSTOM_GENERATOR_CONTEXT_H_ +#define TARGET_CUSTOM_GENERATOR_CUSTOM_GENERATOR_CONTEXT_H_ + +#include "schema/path.h" + +#include "env/command.h" + +namespace buildcc { + +class CustomGeneratorContext { +public: + CustomGeneratorContext(const env::Command &c, + const std::vector &i, + const std::vector &o, + const std::vector &ub) + : command(c), inputs(i), outputs(o), userblob(ub) {} + + const env::Command &command; + const std::vector &inputs; + const std::vector &outputs; + const std::vector &userblob; +}; + +// clang-format off +using GenerateCb = std::function; +// clang-format on + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/target/include/target/file_generator.h b/buildcc/lib/target/include/target/file_generator.h new file mode 100644 index 00000000..9897b5fc --- /dev/null +++ b/buildcc/lib/target/include/target/file_generator.h @@ -0,0 +1,83 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_FILE_GENERATOR_H_ +#define TARGET_FILE_GENERATOR_H_ + +#include "target/custom_generator.h" + +namespace buildcc { + +class FileGenerator : public CustomGenerator { +public: + using CustomGenerator::CustomGenerator; + ~FileGenerator() override = default; + FileGenerator(const FileGenerator &) = delete; + + /** + * @brief Add absolute input path pattern to generator + * NOTE: We can use {current_root_dir} and {current_build_dir} in the + * absolute_input_pattern + * + * If `identifier` is supplied it is added to default arguments as a key + * Example: fmt::format("{identifier}") -> "absolute_input_pattern" + */ + void AddInput(const std::string &absolute_input_pattern); + + /** + * @brief Add absolute output path pattern to generator + * NOTE: We can use {current_root_dir} and {current_build_dir} in the + * absolute_output_pattern + * + * If `identifier` is supplied it is added to default arguments as a key + * Example: fmt::format("{identifier}") -> "absolute_output_pattern" + */ + void AddOutput(const std::string &absolute_output_pattern); + + /** + * @brief Add a command_pattern that is fed to `Command::Execute` internally + * + * NOTE: The order of all commands are maintained (`std::vector::push_back`) + * + * If you would like to run the commands in parallel, set `parallel == true` + * in the constructor + */ + void AddCommand( + const std::string &command_pattern, + const std::unordered_map &arguments = {}); + + /** + * @brief Build FileGenerator Tasks + * + * Use `GetTaskflow` for the registered tasks + */ + void Build() override; + + // Restrict access to certain custom generator APIs +private: + using CustomGenerator::AddIdInfo; + using CustomGenerator::Build; + +private: + // + std::unordered_set inputs_; + std::unordered_set outputs_; + std::vector commands_; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/target/include/target/friend/compile_object.h b/buildcc/lib/target/include/target/friend/compile_object.h new file mode 100644 index 00000000..3e2e8ec8 --- /dev/null +++ b/buildcc/lib/target/include/target/friend/compile_object.h @@ -0,0 +1,84 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_FRIEND_COMPILE_OBJECT_H_ +#define TARGET_FRIEND_COMPILE_OBJECT_H_ + +#include + +#include "schema/path.h" + +#include "taskflow/core/task.hpp" +#include "taskflow/taskflow.hpp" + +namespace fs = std::filesystem; + +namespace buildcc { + +class Target; + +} + +namespace buildcc::internal { + +class CompileObject { + +public: + struct ObjectData { + ObjectData(const fs::path &o, const std::string &c) + : output(o), command(c) {} + + fs::path output; + std::string command; + }; + +public: + CompileObject(Target &target) : target_(target) {} + + void AddObjectData(const fs::path &absolute_source_path); + + void CacheCompileCommands(); + void Task(); + + const ObjectData &GetObjectData(const fs::path &absolute_source) const; + const std::unordered_map &GetObjectDataMap() const { + return object_files_; + } + std::vector GetCompiledSources() const; + tf::Task &GetTask() { return compile_task_; } + +private: + fs::path ConstructObjectPath(const fs::path &absolute_source_file) const; + + void BuildObjectCompile(std::vector &source_files, + std::vector &dummy_source_files); + + void PreObjectCompile(); + + void CompileSources(std::vector &source_files); + void RecompileSources(std::vector &source_files, + std::vector &dummy_source_files); + +private: + Target &target_; + + std::unordered_map object_files_; + tf::Task compile_task_; +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/lib/target/include/target/friend/compile_pch.h b/buildcc/lib/target/include/target/friend/compile_pch.h new file mode 100644 index 00000000..dc4fb3a1 --- /dev/null +++ b/buildcc/lib/target/include/target/friend/compile_pch.h @@ -0,0 +1,89 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_FRIEND_COMPILE_PCH_H_ +#define TARGET_FRIEND_COMPILE_PCH_H_ + +#include +#include + +#include "taskflow/taskflow.hpp" + +namespace fs = std::filesystem; + +namespace buildcc { + +class Target; + +} + +namespace buildcc::internal { + +class CompilePch { +public: + CompilePch(Target &target) + : target_(target), header_path_(ConstructHeaderPath()), + compile_path_(ConstructCompilePath()), + object_path_(ConstructObjectPath()) {} + + // NOTE, These APIs should be called inside `Target::Build` + void CacheCompileCommand(); + void Task(); + + const fs::path &GetHeaderPath() const { return header_path_; } + const fs::path &GetCompilePath() const { return compile_path_; } + const fs::path &GetObjectPath() const { return object_path_; } + + // Call after BUILD + const fs::path &GetSourcePath() const { return source_path_; } + tf::Task &GetTask() { return task_; } + +private: + // Each target only has only 1 PCH file + fs::path ConstructHeaderPath() const; + fs::path ConstructCompilePath() const; + fs::path ConstructObjectPath() const; + + // Needs to checks for C source extension vs Cpp source extension + fs::path ConstructSourcePath(bool has_cpp) const; + + std::string ConstructCompileCommand() const; + + void PreCompile(); + void BuildCompile(); + +private: + Target &target_; + + fs::path header_path_; + fs::path compile_path_; + + // NOTE, Certain compilers (MSVC) require an input source with the header file + // The corresponding object_path has also been added here for usage + // `source_path_` is added as KEY: input_source locally for pch_command_ + // `object_path` is added as KEY: pch_objet_output globally (mainly to be used + // during linking phase) + fs::path source_path_; + fs::path object_path_; + + std::string command_; + + tf::Task task_; +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/lib/target/include/target/friend/link_target.h b/buildcc/lib/target/include/target/friend/link_target.h new file mode 100644 index 00000000..fd39f082 --- /dev/null +++ b/buildcc/lib/target/include/target/friend/link_target.h @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_FRIEND_LINK_TARGET_H_ +#define TARGET_FRIEND_LINK_TARGET_H_ + +#include +#include + +#include "taskflow/taskflow.hpp" + +namespace fs = std::filesystem; + +namespace buildcc { + +class Target; + +} + +namespace buildcc::internal { + +class LinkTarget { +public: + LinkTarget(Target &target) + : target_(target), output_(ConstructOutputPath()) {} + + void CacheLinkCommand(); + void Task(); + + const fs::path &GetOutput() const { return output_; } + const std::string &GetCommand() const { return command_; } + tf::Task &GetTask() { return task_; } + +private: + void BuildLink(); + void PreLink(); + + fs::path ConstructOutputPath() const; + +private: + Target &target_; + + fs::path output_; + std::string command_; + tf::Task task_; +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/lib/target/include/target/interface/builder_interface.h b/buildcc/lib/target/include/target/interface/builder_interface.h new file mode 100644 index 00000000..c020fd91 --- /dev/null +++ b/buildcc/lib/target/include/target/interface/builder_interface.h @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_INTERFACE_BUILDER_INTERFACE_H_ +#define TARGET_INTERFACE_BUILDER_INTERFACE_H_ + +#include +#include +#include + +#include "taskflow/taskflow.hpp" + +#include "env/assert_fatal.h" + +#include "target/common/util.h" + +namespace buildcc::internal { + +class BuilderInterface { + +public: + virtual void Build() = 0; + + const std::string &GetUniqueId() const { return unique_id_; } + tf::Taskflow &GetTaskflow() { return tf_; } + +protected: + bool dirty_{false}; + std::string unique_id_; + tf::Taskflow tf_; +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/lib/target/include/target/target.h b/buildcc/lib/target/include/target/target.h new file mode 100644 index 00000000..1e0ab020 --- /dev/null +++ b/buildcc/lib/target/include/target/target.h @@ -0,0 +1,138 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_TARGET_H_ +#define TARGET_TARGET_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +// Interface +#include "target/interface/builder_interface.h" + +// API +#include "target/api/target_getter.h" +#include "target/target_info.h" + +// Common +#include "schema/target_type.h" + +// Friend +#include "target/friend/compile_object.h" +#include "target/friend/compile_pch.h" +#include "target/friend/link_target.h" + +// Internal +#include "schema/path.h" +#include "schema/target_serialization.h" + +// Env +#include "env/env.h" +#include "env/task_state.h" + +// Components +#include "env/command.h" +#include "toolchain/toolchain.h" + +// Third Party +#include "taskflow/taskflow.hpp" + +namespace buildcc { + +// NOTE, BaseTarget is meant to be a blank slate which can be customized by +// the specialized target-toolchain classes +class Target : public internal::BuilderInterface, + public TargetInfo, + public internal::TargetGetter { + +public: + explicit Target(const std::string &name, TargetType type, + const Toolchain &toolchain, const TargetEnv &env, + const TargetConfig &config = TargetConfig()) + : TargetInfo(toolchain, TargetEnv(env.GetTargetRootDir(), + env.GetTargetBuildDir() / + toolchain.GetName() / name)), + name_(name), type_(type), config_(config), + serialization_(env_.GetTargetBuildDir() / fmt::format("{}.bin", name)), + compile_pch_(*this), compile_object_(*this), link_target_(*this) { + Initialize(); + } + virtual ~Target() = default; + Target(const Target &target) = delete; + + // Builders + void Build() override; + +private: + friend class internal::CompilePch; + friend class internal::CompileObject; + friend class internal::LinkTarget; + + friend class internal::TargetGetter; + +private: + void Initialize(); + + // + env::optional SelectCompileFlags(FileExt ext) const; + env::optional SelectCompiler(FileExt ext) const; + + // Tasks + void EndTask(); + void TaskDeps(); + + // Callbacks for unit tests + void SourceRemoved(); + void SourceAdded(); + void SourceUpdated(); + void PathRemoved(); + void PathAdded(); + void PathUpdated(); + + void PathChanged(); + void DirChanged(); + void FlagChanged(); + void ExternalLibChanged(); + +private: + std::string name_; + TargetType type_; + TargetConfig config_; + internal::TargetSerialization serialization_; + internal::CompilePch compile_pch_; + internal::CompileObject compile_object_; + internal::LinkTarget link_target_; + + // + TargetState state_; + env::Command command_; + + // Task states + tf::Task target_start_task_; + tf::Task target_end_task_; +}; + +typedef Target BaseTarget; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/target/include/target/target_info.h b/buildcc/lib/target/include/target/target_info.h new file mode 100644 index 00000000..b8758ce4 --- /dev/null +++ b/buildcc/lib/target/include/target/target_info.h @@ -0,0 +1,79 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_TARGET_INFO_H_ +#define TARGET_TARGET_INFO_H_ + +#include + +#include "toolchain/toolchain.h" + +#include "target/common/target_env.h" + +#include "target/api/deps_api.h" +#include "target/api/include_api.h" +#include "target/api/lib_api.h" +#include "target/api/pch_api.h" +#include "target/api/source_api.h" +#include "target/api/sync_api.h" + +#include "schema/target_serialization.h" + +namespace buildcc { + +// NOTE: BaseTarget info is meant to hold information that is common to +// multiple targets +// It is also meant to be used in situations where we do not need to build +// For example: Header only targets +class TargetInfo : public internal::SourceApi, + public internal::IncludeApi, + public internal::LibApi, + public internal::PchApi, + public internal::FlagApi, + public internal::DepsApi, + public internal::SyncApi, + public internal::TargetEnvApi { +public: + TargetInfo(const BaseToolchain &toolchain, const TargetEnv &env) + : toolchain_(toolchain), env_(env) { + Initialize(); + } + +private: + friend class internal::SourceApi; + friend class internal::IncludeApi; + friend class internal::LibApi; + friend class internal::PchApi; + friend class internal::FlagApi; + friend class internal::DepsApi; + friend class internal::SyncApi; + friend class internal::TargetEnvApi; + +protected: + const BaseToolchain &toolchain_; + TargetEnv env_; + + internal::TargetSchema user_; + +private: + void Initialize(); +}; + +typedef TargetInfo BaseTargetInfo; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/target/include/target/template_generator.h b/buildcc/lib/target/include/target/template_generator.h new file mode 100644 index 00000000..73c892e7 --- /dev/null +++ b/buildcc/lib/target/include/target/template_generator.h @@ -0,0 +1,60 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_TEMPLATE_GENERATOR_H_ +#define TARGET_TEMPLATE_GENERATOR_H_ + +#include + +#include "target/custom_generator.h" + +namespace buildcc { + +class TemplateGenerator : public CustomGenerator { +public: + using CustomGenerator::CustomGenerator; + ~TemplateGenerator() override = default; + TemplateGenerator(const TemplateGenerator &) = delete; + + void AddTemplate(std::string_view absolute_input_pattern, + std::string_view absolute_output_pattern); + std::string Parse(const std::string &pattern) const; + + /** + * @brief Build FileGenerator Tasks + * + * Use `GetTaskflow` for the registered tasks + */ + void Build() override; + + // Restrict access to certain custom generator APIs +private: + using CustomGenerator::AddIdInfo; + using CustomGenerator::Build; + +private: + struct TemplateInfo { + std::string input_pattern; + std::string output_pattern; + }; + +private: + std::vector template_infos_; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/target/mock/custom_generator/recheck_states.cpp b/buildcc/lib/target/mock/custom_generator/recheck_states.cpp new file mode 100644 index 00000000..c8c6471c --- /dev/null +++ b/buildcc/lib/target/mock/custom_generator/recheck_states.cpp @@ -0,0 +1,43 @@ +#include "target/custom_generator.h" + +#include "expect_custom_generator.h" + +#include "CppUTestExt/MockSupport.h" + +namespace buildcc { + +static constexpr const char *const ID_REMOVED_FUNCTION = + "CustomGenerator::IdRemoved"; +static constexpr const char *const ID_ADDED_FUNCTION = + "CustomGenerator::IdAdded"; +static constexpr const char *const ID_UPDATED_FUNCTION = + "CustomGenerator::IdUpdated"; + +void CustomGenerator::IdRemoved() { + mock().actualCall(ID_REMOVED_FUNCTION).onObject(this); +} +void CustomGenerator::IdAdded() { + mock().actualCall(ID_ADDED_FUNCTION).onObject(this); +} +void CustomGenerator::IdUpdated() { + mock().actualCall(ID_UPDATED_FUNCTION).onObject(this); +} + +namespace m { + +void CustomGeneratorExpect_IdRemoved(unsigned int calls, + CustomGenerator *generator) { + mock().expectNCalls(calls, ID_REMOVED_FUNCTION).onObject(generator); +} +void CustomGeneratorExpect_IdAdded(unsigned int calls, + CustomGenerator *generator) { + mock().expectNCalls(calls, ID_ADDED_FUNCTION).onObject(generator); +} +void CustomGeneratorExpect_IdUpdated(unsigned int calls, + CustomGenerator *generator) { + mock().expectNCalls(calls, ID_UPDATED_FUNCTION).onObject(generator); +} + +} // namespace m + +} // namespace buildcc diff --git a/buildcc/lib/target/mock/custom_generator/runner.cpp b/buildcc/lib/target/mock/custom_generator/runner.cpp new file mode 100644 index 00000000..8014c851 --- /dev/null +++ b/buildcc/lib/target/mock/custom_generator/runner.cpp @@ -0,0 +1,13 @@ +#include "target/custom_generator.h" + +#include "expect_custom_generator.h" + +namespace buildcc::m { + +void CustomGeneratorRunner(CustomGenerator &custom_generator) { + tf::Executor executor(1); + executor.run(custom_generator.GetTaskflow()); + executor.wait_for_all(); +} + +} // namespace buildcc::m diff --git a/buildcc/lib/target/mock/expect_custom_generator.h b/buildcc/lib/target/mock/expect_custom_generator.h new file mode 100644 index 00000000..f54ed17c --- /dev/null +++ b/buildcc/lib/target/mock/expect_custom_generator.h @@ -0,0 +1,23 @@ +#ifndef TARGET_MOCK_EXPECT_CUSTOM_GENERATOR_H_ +#define TARGET_MOCK_EXPECT_CUSTOM_GENERATOR_H_ + +#include "target/custom_generator.h" + +namespace buildcc::m { + +/** + * @brief Runs the generator using Taskflow with 1 thread + * CppUTest cannot mock with multiple threads + */ +void CustomGeneratorRunner(CustomGenerator &custom_generator); + +void CustomGeneratorExpect_IdRemoved(unsigned int calls, + CustomGenerator *generator); +void CustomGeneratorExpect_IdAdded(unsigned int calls, + CustomGenerator *generator); +void CustomGeneratorExpect_IdUpdated(unsigned int calls, + CustomGenerator *generator); + +} // namespace buildcc::m + +#endif diff --git a/buildcc/lib/target/mock/expect_target.h b/buildcc/lib/target/mock/expect_target.h index 9aba49c0..816e3175 100644 --- a/buildcc/lib/target/mock/expect_target.h +++ b/buildcc/lib/target/mock/expect_target.h @@ -1,17 +1,15 @@ #ifndef TARGET_MOCK_EXPECT_TARGET_H_ #define TARGET_MOCK_EXPECT_TARGET_H_ -#include "target.h" +#include "target/target.h" -namespace buildcc { +namespace buildcc::m { -namespace internal::m { - -void Expect_command(unsigned int calls, bool expectation); - -} // namespace internal::m - -namespace base::m { +/** + * @brief Runs the target using Taskflow with 1 thread + * CppUTest cannot mock with multiple threads + */ +void TargetRunner(Target &target); void TargetExpect_SourceRemoved(unsigned int calls, Target *target); void TargetExpect_SourceAdded(unsigned int calls, Target *target); @@ -21,12 +19,11 @@ void TargetExpect_PathRemoved(unsigned int calls, Target *target); void TargetExpect_PathAdded(unsigned int calls, Target *target); void TargetExpect_PathUpdated(unsigned int calls, Target *target); +void TargetExpect_PathChanged(unsigned int calls, Target *target); void TargetExpect_DirChanged(unsigned int calls, Target *target); void TargetExpect_FlagChanged(unsigned int calls, Target *target); void TargetExpect_ExternalLibChanged(unsigned int calls, Target *target); -} // namespace base::m - -} // namespace buildcc +} // namespace buildcc::m #endif diff --git a/buildcc/lib/target/mock/target/recheck_states.cpp b/buildcc/lib/target/mock/target/recheck_states.cpp index e5f6414d..f7ff1f34 100644 --- a/buildcc/lib/target/mock/target/recheck_states.cpp +++ b/buildcc/lib/target/mock/target/recheck_states.cpp @@ -1,10 +1,10 @@ -#include "target.h" +#include "target/target.h" #include "expect_target.h" #include "CppUTestExt/MockSupport.h" -namespace buildcc::base { +namespace buildcc { static constexpr const char *const SOURCE_REMOVED_FUNCTION = "Target::SourceRemoved"; @@ -13,12 +13,8 @@ static constexpr const char *const SOURCE_ADDED_FUNCTION = static constexpr const char *const SOURCE_UPDATED_FUNCTION = "Target::SourceUpdated"; -static constexpr const char *const PATH_REMOVED_FUNCTION = - "Target::PathRemoved"; -static constexpr const char *const PATH_ADDED_FUNCTION = "Target::PathAdded"; -static constexpr const char *const PATH_UPDATED_FUNCTION = - "Target::PathUpdated"; - +static constexpr const char *const PATH_CHANGED_FUNCTION = + "Target::PathChanged"; static constexpr const char *const DIR_CHANGED_FUNCTION = "Target::DirChanged"; static constexpr const char *const FLAG_CHANGED_FUNCTION = "Target::FlagChanged"; @@ -37,14 +33,12 @@ void Target::SourceUpdated() { } // Path rechecks -void Target::PathRemoved() { - mock().actualCall(PATH_REMOVED_FUNCTION).onObject(this); -} -void Target::PathAdded() { - mock().actualCall(PATH_ADDED_FUNCTION).onObject(this); -} -void Target::PathUpdated() { - mock().actualCall(PATH_UPDATED_FUNCTION).onObject(this); +void Target::PathRemoved() { PathChanged(); } +void Target::PathAdded() { PathChanged(); } +void Target::PathUpdated() { PathChanged(); } + +void Target::PathChanged() { + mock().actualCall(PATH_CHANGED_FUNCTION).onObject(this); } void Target::DirChanged() { @@ -72,13 +66,17 @@ void TargetExpect_SourceUpdated(unsigned int calls, Target *target) { } void TargetExpect_PathRemoved(unsigned int calls, Target *target) { - mock().expectNCalls(calls, PATH_REMOVED_FUNCTION).onObject(target); + TargetExpect_PathChanged(calls, target); } void TargetExpect_PathAdded(unsigned int calls, Target *target) { - mock().expectNCalls(calls, PATH_ADDED_FUNCTION).onObject(target); + TargetExpect_PathChanged(calls, target); } void TargetExpect_PathUpdated(unsigned int calls, Target *target) { - mock().expectNCalls(calls, PATH_UPDATED_FUNCTION).onObject(target); + TargetExpect_PathChanged(calls, target); +} + +void TargetExpect_PathChanged(unsigned int calls, Target *target) { + mock().expectNCalls(calls, PATH_CHANGED_FUNCTION).onObject(target); } void TargetExpect_DirChanged(unsigned int calls, Target *target) { @@ -95,4 +93,4 @@ void TargetExpect_ExternalLibChanged(unsigned int calls, Target *target) { } // namespace m -} // namespace buildcc::base +} // namespace buildcc diff --git a/buildcc/lib/target/mock/target/runner.cpp b/buildcc/lib/target/mock/target/runner.cpp new file mode 100644 index 00000000..a15c8de5 --- /dev/null +++ b/buildcc/lib/target/mock/target/runner.cpp @@ -0,0 +1,13 @@ +#include "target/target.h" + +#include "expect_target.h" + +namespace buildcc::m { + +void TargetRunner(Target &target) { + tf::Executor executor(1); + executor.run(target.GetTaskflow()); + executor.wait_for_all(); +} + +} // namespace buildcc::m diff --git a/buildcc/lib/target/mock/target/tasks.cpp b/buildcc/lib/target/mock/target/tasks.cpp deleted file mode 100644 index 4bcdf55e..00000000 --- a/buildcc/lib/target/mock/target/tasks.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "target.h" - -#include "CppUTestExt/MockSupport.h" - -namespace buildcc::base { - -void Target::CompileTargetTask( - const std::vector &&compile_sources, - const std::vector &&dummy_compile_sources) { - (void)dummy_compile_sources; - for (const auto &cs : compile_sources) { - CompileSource(cs); - } -} - -void Target::LinkTargetTask(const bool link) { - if (link) { - LinkTarget(); - } -} - -} // namespace buildcc::base diff --git a/buildcc/lib/target/mock/test_target_util.h b/buildcc/lib/target/mock/test_target_util.h new file mode 100644 index 00000000..70bad12a --- /dev/null +++ b/buildcc/lib/target/mock/test_target_util.h @@ -0,0 +1,22 @@ +#ifndef TARGET_MOCK_TEST_TARGET_UTIL_H_ +#define TARGET_MOCK_TEST_TARGET_UTIL_H_ + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace buildcc::m { + +inline void blocking_sleep(int seconds) { +#ifdef _WIN32 + Sleep(seconds * 1000); +#else + sleep(seconds); +#endif +} + +} // namespace buildcc::m + +#endif diff --git a/buildcc/lib/target/mock/util/command.cpp b/buildcc/lib/target/mock/util/command.cpp deleted file mode 100644 index 2139ddbb..00000000 --- a/buildcc/lib/target/mock/util/command.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "internal/util.h" - -#include "CppUTestExt/MockSupport.h" - -namespace buildcc::internal { - -static constexpr const char *const COMMAND_FUNCTION = "command"; - -// command -bool command(const std::vector &command_tokens) { - (void)command_tokens; - return mock().actualCall(COMMAND_FUNCTION).returnBoolValue(); -} - -namespace m { - -void Expect_command(unsigned int calls, bool expectation) { - mock().expectNCalls(calls, COMMAND_FUNCTION).andReturnValue(expectation); -} - -} // namespace m - -} // namespace buildcc::internal diff --git a/buildcc/lib/target/src/util/command.cpp b/buildcc/lib/target/src/api/lib_api.cpp similarity index 61% rename from buildcc/lib/target/src/util/command.cpp rename to buildcc/lib/target/src/api/lib_api.cpp index f6d68a41..0d24ca8c 100644 --- a/buildcc/lib/target/src/util/command.cpp +++ b/buildcc/lib/target/src/api/lib_api.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,23 +14,19 @@ * limitations under the License. */ -#include "internal/util.h" +#include "target/api/lib_api.h" -#include "logging.h" - -#include "process.hpp" - -namespace tpl = TinyProcessLib; +#include "target/target.h" +#include "target/target_info.h" namespace buildcc::internal { -// command -bool command(const std::vector &command_tokens) { - std::string command = aggregate(command_tokens); - buildcc::env::log_debug("system", command); +template void LibApi::AddLibDep(const BaseTarget &lib_dep) { + T &t = static_cast(*this); - tpl::Process process(command); - return process.get_exit_status() == 0; + t.user_.libs.Emplace(lib_dep.GetTargetPath(), ""); } +template class LibApi; + } // namespace buildcc::internal diff --git a/buildcc/lib/target/src/api/sync_api.cpp b/buildcc/lib/target/src/api/sync_api.cpp new file mode 100644 index 00000000..7cecfc06 --- /dev/null +++ b/buildcc/lib/target/src/api/sync_api.cpp @@ -0,0 +1,215 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "target/api/sync_api.h" + +#include "target/target_info.h" + +#include "env/assert_fatal.h" + +namespace buildcc::internal { + +template +void SyncApi::Copy(const T &target, + std::initializer_list options) { + env::log_trace(__FUNCTION__, "Copy/Replace by const ref"); + SpecializedCopy(target, options); +} + +template +void SyncApi::Copy(T &&target, std::initializer_list options) { + env::log_trace(__FUNCTION__, "Copy/Replace by move"); + SpecializedCopy(std::move(target), options); +} + +// template +template +template +void SyncApi::SpecializedCopy(TargetType target, + std::initializer_list options) { + auto &t = static_cast(*this); + for (const SyncOption o : options) { + switch (o) { + case SyncOption::PreprocessorFlags: + t.user_.preprocessor_flags = std::move(target.user_.preprocessor_flags); + break; + case SyncOption::CommonCompileFlags: + t.user_.common_compile_flags = + std::move(target.user_.common_compile_flags); + break; + case SyncOption::PchCompileFlags: + t.user_.pch_compile_flags = std::move(target.user_.pch_compile_flags); + break; + case SyncOption::PchObjectFlags: + t.user_.pch_object_flags = std::move(target.user_.pch_object_flags); + break; + case SyncOption::AsmCompileFlags: + t.user_.asm_compile_flags = std::move(target.user_.asm_compile_flags); + break; + case SyncOption::CCompileFlags: + t.user_.c_compile_flags = std::move(target.user_.c_compile_flags); + break; + case SyncOption::CppCompileFlags: + t.user_.cpp_compile_flags = std::move(target.user_.cpp_compile_flags); + break; + case SyncOption::LinkFlags: + t.user_.link_flags = std::move(target.user_.link_flags); + break; + case SyncOption::CompileDependencies: + t.user_.compile_dependencies = + std::move(target.user_.compile_dependencies); + break; + case SyncOption::LinkDependencies: + t.user_.link_dependencies = std::move(target.user_.link_dependencies); + break; + case SyncOption::SourceFiles: + t.user_.sources = std::move(target.user_.sources); + break; + case SyncOption::HeaderFiles: + t.user_.headers = std::move(target.user_.headers); + break; + case SyncOption::PchFiles: + t.user_.pchs = std::move(target.user_.pchs); + break; + case SyncOption::LibDeps: + t.user_.libs = std::move(target.user_.libs); + break; + case SyncOption::IncludeDirs: + t.user_.include_dirs = std::move(target.user_.include_dirs); + break; + case SyncOption::LibDirs: + t.user_.lib_dirs = std::move(target.user_.lib_dirs); + break; + case SyncOption::ExternalLibDeps: + t.user_.external_libs = std::move(target.user_.external_libs); + break; + default: + env::assert_fatal("Invalid Option added"); + break; + } + } +} + +template +void SyncApi::Insert(const T &target, + std::initializer_list options) { + env::log_trace(__FUNCTION__, "Insert by const ref"); + SpecializedInsert(target, options); +} + +template +void SyncApi::Insert(T &&target, std::initializer_list options) { + env::log_trace(__FUNCTION__, "Insert by move"); + SpecializedInsert(std::move(target), options); +} + +template +template +void SyncApi::SpecializedInsert(TargetType target, + std::initializer_list options) { + auto &t = static_cast(*this); + for (const SyncOption o : options) { + switch (o) { + case SyncOption::PreprocessorFlags: + t.user_.preprocessor_flags.insert( + t.user_.preprocessor_flags.end(), + std::make_move_iterator(target.user_.preprocessor_flags.begin()), + std::make_move_iterator(target.user_.preprocessor_flags.end())); + break; + case SyncOption::CommonCompileFlags: + t.user_.common_compile_flags.insert( + t.user_.common_compile_flags.end(), + std::make_move_iterator(target.user_.common_compile_flags.begin()), + std::make_move_iterator(target.user_.common_compile_flags.end())); + break; + case SyncOption::PchCompileFlags: + t.user_.pch_compile_flags.insert( + t.user_.pch_compile_flags.end(), + std::make_move_iterator(target.user_.pch_compile_flags.begin()), + std::make_move_iterator(target.user_.pch_compile_flags.end())); + break; + case SyncOption::PchObjectFlags: + t.user_.pch_object_flags.insert( + t.user_.pch_object_flags.end(), + std::make_move_iterator(target.user_.pch_object_flags.begin()), + std::make_move_iterator(target.user_.pch_object_flags.end())); + break; + case SyncOption::AsmCompileFlags: + t.user_.asm_compile_flags.insert( + t.user_.asm_compile_flags.end(), + std::make_move_iterator(target.user_.asm_compile_flags.begin()), + std::make_move_iterator(target.user_.asm_compile_flags.end())); + break; + case SyncOption::CCompileFlags: + t.user_.c_compile_flags.insert( + t.user_.c_compile_flags.end(), + std::make_move_iterator(target.user_.c_compile_flags.begin()), + std::make_move_iterator(target.user_.c_compile_flags.end())); + break; + case SyncOption::CppCompileFlags: + t.user_.cpp_compile_flags.insert( + t.user_.cpp_compile_flags.end(), + std::make_move_iterator(target.user_.cpp_compile_flags.begin()), + std::make_move_iterator(target.user_.cpp_compile_flags.end())); + break; + case SyncOption::LinkFlags: + t.user_.link_flags.insert( + t.user_.link_flags.end(), + std::make_move_iterator(target.user_.link_flags.begin()), + std::make_move_iterator(target.user_.link_flags.end())); + break; + case SyncOption::CompileDependencies: + t.user_.compile_dependencies.Insert( + std::move(target.user_.compile_dependencies)); + break; + case SyncOption::LinkDependencies: + t.user_.link_dependencies.Insert( + std::move(target.user_.link_dependencies)); + break; + case SyncOption::SourceFiles: + t.user_.sources.Insert(std::move(target.user_.sources)); + break; + case SyncOption::HeaderFiles: + t.user_.headers.Insert(std::move(target.user_.headers)); + break; + case SyncOption::PchFiles: + t.user_.pchs.Insert(std::move(target.user_.pchs)); + break; + case SyncOption::LibDeps: + t.user_.libs.Insert(std::move(target.user_.libs)); + break; + case SyncOption::IncludeDirs: + t.user_.include_dirs.Insert(std::move(target.user_.include_dirs)); + break; + case SyncOption::LibDirs: + t.user_.lib_dirs.Insert(std::move(target.user_.lib_dirs)); + break; + case SyncOption::ExternalLibDeps: + t.user_.external_libs.insert( + t.user_.external_libs.end(), + std::make_move_iterator(target.user_.external_libs.begin()), + std::make_move_iterator(target.user_.external_libs.end())); + break; + default: + env::assert_fatal("Invalid Option added"); + break; + } + } +} + +template class SyncApi; + +} // namespace buildcc::internal diff --git a/buildcc/lib/target/src/api/target_getter.cpp b/buildcc/lib/target/src/api/target_getter.cpp new file mode 100644 index 00000000..dc25a826 --- /dev/null +++ b/buildcc/lib/target/src/api/target_getter.cpp @@ -0,0 +1,92 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "target/api/target_getter.h" + +#include "target/target.h" + +namespace buildcc::internal { + +// Target State +template const TargetState &TargetGetter::GetState() const { + const auto &t = static_cast(*this); + return t.state_; +} + +template bool TargetGetter::IsBuilt() const { + const auto &t = static_cast(*this); + return t.state_.IsBuilt(); +} + +// Target Config +template const TargetConfig &TargetGetter::GetConfig() const { + const auto &t = static_cast(*this); + return t.config_; +} + +template const fs::path &TargetGetter::GetBinaryPath() const { + const auto &t = static_cast(*this); + return t.serialization_.GetSerializedFile(); +} + +template const fs::path &TargetGetter::GetTargetPath() const { + const auto &t = static_cast(*this); + return t.link_target_.GetOutput(); +} + +template +const fs::path &TargetGetter::GetPchHeaderPath() const { + const auto &t = static_cast(*this); + return t.compile_pch_.GetHeaderPath(); +} + +template +const fs::path &TargetGetter::GetPchCompilePath() const { + const auto &t = static_cast(*this); + return t.compile_pch_.GetCompilePath(); +} + +template const std::string &TargetGetter::GetName() const { + const auto &t = static_cast(*this); + return t.name_; +} + +template const Toolchain &TargetGetter::GetToolchain() const { + const auto &t = static_cast(*this); + return t.toolchain_; +} + +template TargetType TargetGetter::GetType() const { + const auto &t = static_cast(*this); + return t.type_; +} + +template +const std::string & +TargetGetter::GetCompileCommand(const fs::path &source) const { + const auto &t = static_cast(*this); + return t.compile_object_.GetObjectData(source).command; +} + +template +const std::string &TargetGetter::GetLinkCommand() const { + const auto &t = static_cast(*this); + return t.link_target_.GetCommand(); +} + +template class TargetGetter; + +} // namespace buildcc::internal diff --git a/buildcc/lib/target/src/target/flags.cpp b/buildcc/lib/target/src/common/target_config.cpp similarity index 52% rename from buildcc/lib/target/src/target/flags.cpp rename to buildcc/lib/target/src/common/target_config.cpp index a7d5c964..23207ac1 100644 --- a/buildcc/lib/target/src/target/flags.cpp +++ b/buildcc/lib/target/src/common/target_config.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,21 +14,12 @@ * limitations under the License. */ -#include "target.h" +#include "target/common/target_config.h" -namespace buildcc::base { +#include "env/assert_fatal.h" -void Target::AddPreprocessorFlag(const std::string &flag) { - current_preprocessor_flags_.insert(flag); -} -void Target::AddCCompileFlag(const std::string &flag) { - current_c_compile_flags_.insert(flag); -} -void Target::AddCppCompileFlag(const std::string &flag) { - current_cpp_compile_flags_.insert(flag); -} -void Target::AddLinkFlag(const std::string &flag) { - current_link_flags_.insert(flag); -} +#include "schema/path.h" -} // namespace buildcc::base +#include "fmt/format.h" + +namespace buildcc {} // namespace buildcc diff --git a/buildcc/lib/target/src/common/target_state.cpp b/buildcc/lib/target/src/common/target_state.cpp new file mode 100644 index 00000000..a8e5cc65 --- /dev/null +++ b/buildcc/lib/target/src/common/target_state.cpp @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "target/common/target_state.h" + +#include "env/assert_fatal.h" + +namespace buildcc { + +void TargetState::BuildCompleted() { build_ = true; } + +void TargetState::SourceDetected(FileExt file_extension) { + switch (file_extension) { + case FileExt::Asm: + contains_asm_ = true; + break; + case FileExt::C: + contains_c_ = true; + break; + case FileExt::Cpp: + contains_cpp_ = true; + break; + case FileExt::Header: + case FileExt::Invalid: + default: + break; + } +} + +void TargetState::PchDetected() { contains_pch_ = true; } + +} // namespace buildcc diff --git a/buildcc/lib/target/src/custom_generator/custom_generator.cpp b/buildcc/lib/target/src/custom_generator/custom_generator.cpp new file mode 100644 index 00000000..138bb9f3 --- /dev/null +++ b/buildcc/lib/target/src/custom_generator/custom_generator.cpp @@ -0,0 +1,317 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "target/custom_generator.h" + +namespace { + +constexpr const char *const kGenerateTaskName = "Generate"; +constexpr const char *const kProjectRootDirName = "project_root_dir"; +constexpr const char *const kProjectBuildDirName = "project_build_dir"; +constexpr const char *const kCurrentRootDirName = "current_root_dir"; +constexpr const char *const kCurrentBuildDirName = "current_build_dir"; + +} // namespace + +namespace buildcc { + +struct Comparator { + Comparator(const internal::CustomGeneratorSchema &loaded, + const UserCustomGeneratorSchema ¤t) + : loaded_(loaded), current_(current) {} + + enum class State { + kRemoved, + kAdded, + kCheckLater, + }; + + void AddAllIds() { + const auto &curr_ids = current_.ids; + for (const auto &[id, _] : curr_ids) { + id_state_info_.at(State::kAdded).insert(id); + } + } + + void CompareAndAddIds() { + const auto &prev_ids = loaded_.internal_ids; + const auto &curr_ids = current_.ids; + + for (const auto &[prev_id, _] : prev_ids) { + if (curr_ids.find(prev_id) == curr_ids.end()) { + // Id Removed condition, previous id is not present in the current run + id_state_info_.at(State::kRemoved).insert(prev_id); + } + } + + for (const auto &[curr_id, _] : curr_ids) { + if (prev_ids.find(curr_id) == prev_ids.end()) { + // Id Added condition + id_state_info_.at(State::kAdded).insert(curr_id); + } else { + // Id Check Later condition + id_state_info_.at(State::kCheckLater).insert(curr_id); + } + } + } + + bool IsChanged(const std::string &id) const { + const auto &previous_id_info = loaded_.internal_ids.at(id); + const auto ¤t_id_info = current_.ids.at(id); + + bool changed = !previous_id_info.inputs.IsEqual(current_id_info.inputs) || + !previous_id_info.outputs.IsEqual(current_id_info.outputs); + if (!changed && current_id_info.blob_handler != nullptr) { + // We only check blob handler if not changed by inputs/outputs + // Checking blob_handler could be expensive so this optimization is made + // to run only when changed == false + changed = current_id_info.blob_handler->CheckChanged( + previous_id_info.userblob, current_id_info.userblob); + } + return changed; + } + + const std::unordered_set &GetRemovedIds() const { + return id_state_info_.at(State::kRemoved); + } + + const std::unordered_set &GetAddedIds() const { + return id_state_info_.at(State::kAdded); + } + + const std::unordered_set &GetCheckLaterIds() const { + return id_state_info_.at(State::kCheckLater); + } + + bool IsIdAdded(const std::string &id) const { + return id_state_info_.at(State::kAdded).count(id) == 1; + } + +private: + const buildcc::internal::CustomGeneratorSchema &loaded_; + const buildcc::UserCustomGeneratorSchema ¤t_; + std::unordered_map> id_state_info_{ + {State::kRemoved, std::unordered_set()}, + {State::kAdded, std::unordered_set()}, + {State::kCheckLater, std::unordered_set()}, + }; +}; + +struct TaskState { + bool should_run{false}; + bool run_success{false}; +}; + +struct TaskFunctor { + TaskFunctor(const std::string &id, + UserCustomGeneratorSchema::UserIdInfo &id_info, + const Comparator &comparator, const env::Command &command, + TaskState &state) + : id_(id), id_info_(id_info), comparator(comparator), command_(command), + state_(state) {} + + void operator()() { + if (env::get_task_state() != env::TaskState::SUCCESS) { + return; + } + try { + id_info_.ConvertToInternal(); + // Compute runnable + state_.should_run = + comparator.IsIdAdded(id_) ? true : comparator.IsChanged(id_); + + // Invoke generator callback + if (state_.should_run) { + const auto input_paths = id_info_.inputs.GetPaths(); + CustomGeneratorContext ctx(command_, input_paths, + id_info_.outputs.GetPaths(), + id_info_.userblob); + + bool success = id_info_.generate_cb(ctx); + env::assert_fatal(success, + fmt::format("Generate Cb failed for id {}", id_)); + } + state_.run_success = true; + } catch (...) { + env::set_task_state(env::TaskState::FAILURE); + } + } + +private: + const std::string &id_; + UserCustomGeneratorSchema::UserIdInfo &id_info_; + + const Comparator &comparator; + const env::Command &command_; + + TaskState &state_; +}; + +bool ComputeBuild(const internal::CustomGeneratorSerialization &serialization, + Comparator &comparator, + std::function &&id_removed_cb, + std::function &&id_added_cb) { + bool build = false; + if (!serialization.IsLoaded()) { + comparator.AddAllIds(); + build = true; + } else { + comparator.CompareAndAddIds(); + const bool is_removed = !comparator.GetRemovedIds().empty(); + const bool is_added = !comparator.GetAddedIds().empty(); + build = is_removed || is_added; + + if (is_removed) { + id_removed_cb(); + } + + for (const auto &id : comparator.GetAddedIds()) { + (void)id; + id_added_cb(); + } + } + return build; +} + +void CustomGenerator::AddPattern(const std::string &identifier, + const std::string &pattern) { + command_.AddDefaultArgument(identifier, command_.Construct(pattern)); +} + +void CustomGenerator::AddPatterns( + const std::unordered_map &pattern_map) { + for (const auto &[identifier, pattern] : pattern_map) { + AddPattern(identifier, pattern); + } +} + +std::string CustomGenerator::ParsePattern( + const std::string &pattern, + const std::unordered_map &arguments) const { + return command_.Construct(pattern, arguments); +} + +const std::string & +CustomGenerator::Get(const std::string &file_identifier) const { + return command_.GetDefaultValueByKey(file_identifier); +} + +void CustomGenerator::AddIdInfo( + const std::string &id, const std::unordered_set &inputs, + const std::unordered_set &outputs, + const GenerateCb &generate_cb, + const std::shared_ptr &blob_handler) { + env::assert_fatal(user_.ids.find(id) == user_.ids.end(), + fmt::format("Duplicate id {} detected", id)); + ASSERT_FATAL(generate_cb, "Invalid callback provided"); + + UserCustomGeneratorSchema::UserIdInfo schema; + for (const auto &i : inputs) { + auto input = command_.Construct(i); + schema.inputs.Emplace(input, ""); + } + for (const auto &o : outputs) { + auto output = command_.Construct(o); + schema.outputs.Emplace(output); + } + schema.generate_cb = generate_cb; + schema.blob_handler = blob_handler; + user_.ids.try_emplace(id, std::move(schema)); +} + +void CustomGenerator::Build() { + (void)serialization_.LoadFromFile(); + GenerateTask(); +} + +// PRIVATE +void CustomGenerator::Initialize() { + // Checks + env::assert_fatal( + Project::IsInit(), + "Environment is not initialized. Use the buildcc::Project::Init API"); + + // + fs::create_directories(env_.GetTargetBuildDir()); + command_.AddDefaultArguments({ + {kProjectRootDirName, path_as_string(Project::GetRootDir())}, + {kProjectBuildDirName, path_as_string(Project::GetBuildDir())}, + {kCurrentRootDirName, path_as_string(env_.GetTargetRootDir())}, + {kCurrentBuildDirName, path_as_string(env_.GetTargetBuildDir())}, + }); + + // + unique_id_ = name_; + tf_.name(name_); +} + +void CustomGenerator::GenerateTask() { + tf::Task generate_task = tf_.emplace([&](tf::Subflow &subflow) { + if (env::get_task_state() != env::TaskState::SUCCESS) { + return; + } + + try { + // Selected ids for build + Comparator comparator(serialization_.GetLoad(), user_); + dirty_ = ComputeBuild( + serialization_, comparator, [this]() { IdRemoved(); }, + [this]() { IdAdded(); }); + + std::unordered_map states; + + // Create runner for each added/updated id + for (const auto &id : comparator.GetAddedIds()) { + states.try_emplace(id, TaskState()); + auto &id_info = user_.ids.at(id); + TaskFunctor functor(id, id_info, comparator, command_, states.at(id)); + subflow.emplace(functor).name(id); + } + + for (const auto &id : comparator.GetCheckLaterIds()) { + states.try_emplace(id, TaskState()); + auto &id_info = user_.ids.at(id); + TaskFunctor functor(id, id_info, comparator, command_, states.at(id)); + subflow.emplace(functor).name(id); + } + + // NOTE, Do not call detach otherwise this will fail + subflow.join(); + + UserCustomGeneratorSchema user_final_schema; + for (const auto &[id, state] : states) { + dirty_ = dirty_ || state.should_run; + if (state.run_success) { + user_final_schema.ids.try_emplace(id, user_.ids.at(id)); + } + } + + // Store + if (dirty_) { + user_final_schema.ConvertToInternal(); + + serialization_.UpdateStore(user_final_schema); + env::assert_fatal(serialization_.StoreToFile(), + fmt::format("Store failed for {}", name_)); + } + } catch (...) { + env::set_task_state(env::TaskState::FAILURE); + } + }); + generate_task.name(kGenerateTaskName); +} + +} // namespace buildcc diff --git a/buildcc/lib/target/src/custom_generator/recheck_states.cpp b/buildcc/lib/target/src/custom_generator/recheck_states.cpp new file mode 100644 index 00000000..0ccc632c --- /dev/null +++ b/buildcc/lib/target/src/custom_generator/recheck_states.cpp @@ -0,0 +1,25 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "target/custom_generator.h" + +namespace buildcc { + +void CustomGenerator::IdRemoved() {} +void CustomGenerator::IdAdded() {} +void CustomGenerator::IdUpdated() {} + +} // namespace buildcc diff --git a/buildcc/lib/target/src/fbs/fbs_loader.cpp b/buildcc/lib/target/src/fbs/fbs_loader.cpp deleted file mode 100644 index c3b7054a..00000000 --- a/buildcc/lib/target/src/fbs/fbs_loader.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "internal/fbs_loader.h" - -#include "flatbuffers/util.h" -#include "target_generated.h" - -#include "logging.h" - -namespace fbs = schema::internal; - -namespace { -void Extract( - const flatbuffers::Vector> - *fbs_paths, - buildcc::internal::path_unordered_set &out) { - if (fbs_paths == nullptr) { - return; - } - - for (auto iter = fbs_paths->begin(); iter != fbs_paths->end(); iter++) { - out.insert(buildcc::internal::Path::CreateNewPath( - iter->pathname()->c_str(), iter->last_write_timestamp())); - } -} - -void Extract(const flatbuffers::Vector> - *fbs_paths, - std::unordered_set &out) { - if (fbs_paths == nullptr) { - return; - } - - for (auto iter = fbs_paths->begin(); iter != fbs_paths->end(); iter++) { - out.insert(iter->str()); - } -} - -// TODO, ExtractLibs - -} // namespace - -namespace buildcc::internal { - -// Public functions -bool FbsLoader::Load() { - env::log_trace(name_, __FUNCTION__); - - auto file_path = GetBinaryPath(); - std::string buffer; - bool is_loaded = - flatbuffers::LoadFile(file_path.string().c_str(), true, &buffer); - if (!is_loaded) { - return false; - } - const auto *target = fbs::GetTarget((const void *)buffer.c_str()); - // target->name()->c_str(); - // target->relative_path()->c_str(); - // target->type(); - // target->toolchain(); - Extract(target->source_files(), loaded_sources_); - Extract(target->header_files(), loaded_headers_); - Extract(target->lib_deps(), loaded_lib_deps_); - - Extract(target->external_lib_deps(), loaded_external_lib_dirs_); - - Extract(target->include_dirs(), loaded_include_dirs_); - Extract(target->lib_dirs(), loaded_lib_dirs_); - - Extract(target->preprocessor_flags(), loaded_preprocessor_flags_); - Extract(target->c_compile_flags(), loaded_c_compile_flags_); - Extract(target->cpp_compile_flags(), loaded_cpp_compile_flags_); - Extract(target->link_flags(), loaded_link_flags_); - - loaded_ = true; - return true; -} - -// Private functions -void FbsLoader::Initialize() {} - -} // namespace buildcc::internal diff --git a/buildcc/lib/target/src/fbs/fbs_storer.cpp b/buildcc/lib/target/src/fbs/fbs_storer.cpp deleted file mode 100644 index d168c008..00000000 --- a/buildcc/lib/target/src/fbs/fbs_storer.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "target.h" - -#include -#include - -#include "logging.h" - -#include "flatbuffers/util.h" -#include "target_generated.h" - -namespace fbs = schema::internal; - -namespace { - -fbs::TargetType get_fbs_target_type(buildcc::base::TargetType type) { - fbs::TargetType target_type = fbs::TargetType_Executable; - switch (type) { - case buildcc::base::TargetType::Executable: - break; - case buildcc::base::TargetType::StaticLibrary: - target_type = fbs::TargetType::TargetType_StaticLibrary; - break; - case buildcc::base::TargetType::DynamicLibrary: - target_type = fbs::TargetType_DynamicLibrary; - break; - } - return target_type; -} - -// TODO, Complete this with additional flags -flatbuffers::Offset -get_fbs_toolchain(flatbuffers::FlatBufferBuilder &builder, - const buildcc::base::Toolchain &toolchain) { - return fbs::CreateToolchainDirect( - builder, toolchain.GetName().c_str(), toolchain.GetAsmCompiler().c_str(), - toolchain.GetCCompiler().c_str(), toolchain.GetCppCompiler().c_str(), - toolchain.GetArchiver().c_str(), toolchain.GetLinker().c_str()); -} - -std::vector> -get_fbs_vector_path(flatbuffers::FlatBufferBuilder &builder, - const buildcc::internal::path_unordered_set &pathlist) { - std::vector> paths; - for (const auto &p : pathlist) { - auto fbs_file = fbs::CreatePathDirect( - builder, p.GetPathname().string().c_str(), p.GetLastWriteTimestamp()); - paths.push_back(fbs_file); - } - return paths; -} - -std::vector> -get_fbs_vector_string(flatbuffers::FlatBufferBuilder &builder, - const std::unordered_set &strlist) { - std::vector> strs; - std::transform( - strlist.begin(), strlist.end(), std::back_inserter(strs), - [&](const std::string &str) -> flatbuffers::Offset { - return builder.CreateString(str); - }); - return strs; -} - -} // namespace - -namespace buildcc::base { - -bool Target::Store() { - env::log_trace(name_, __FUNCTION__); - - flatbuffers::FlatBufferBuilder builder; - - auto fbs_target_type = get_fbs_target_type(type_); - auto fbs_toolchain = get_fbs_toolchain(builder, toolchain_); - - auto fbs_source_files = get_fbs_vector_path(builder, current_source_files_); - auto fbs_header_files = get_fbs_vector_path(builder, current_header_files_); - auto fbs_lib_deps = get_fbs_vector_path(builder, current_lib_deps_); - - auto fbs_external_lib_deps = - get_fbs_vector_string(builder, current_external_lib_deps_); - - auto fbs_include_dirs = get_fbs_vector_string(builder, current_include_dirs_); - auto fbs_lib_dirs = get_fbs_vector_string(builder, current_lib_dirs_); - - auto fbs_preprocessor_flags = - get_fbs_vector_string(builder, current_preprocessor_flags_); - auto fbs_c_compiler_flags = - get_fbs_vector_string(builder, current_c_compile_flags_); - auto fbs_cpp_compiler_flags = - get_fbs_vector_string(builder, current_cpp_compile_flags_); - auto fbs_link_flags = get_fbs_vector_string(builder, current_link_flags_); - - auto fbs_target = fbs::CreateTargetDirect( - builder, name_.c_str(), target_intermediate_dir_.string().c_str(), - fbs_target_type, fbs_toolchain, &fbs_source_files, &fbs_header_files, - &fbs_lib_deps, &fbs_external_lib_deps, &fbs_include_dirs, &fbs_lib_dirs, - &fbs_preprocessor_flags, &fbs_c_compiler_flags, &fbs_cpp_compiler_flags, - &fbs_link_flags); - fbs::FinishTargetBuffer(builder, fbs_target); - - auto file_path = GetBinaryPath(); - return flatbuffers::SaveFile(file_path.string().c_str(), - (const char *)builder.GetBufferPointer(), - builder.GetSize(), true); -} - -} // namespace buildcc::base diff --git a/buildcc/lib/target/src/generator/file_generator.cpp b/buildcc/lib/target/src/generator/file_generator.cpp new file mode 100644 index 00000000..3256ad2b --- /dev/null +++ b/buildcc/lib/target/src/generator/file_generator.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "target/file_generator.h" + +#include + +#include "env/assert_fatal.h" + +namespace { + +bool FileGeneratorGenerateCb(const buildcc::CustomGeneratorContext &ctx) { + (void)ctx; + bool success = true; + std::vector commands = + buildcc::TypedCustomBlobHandler>::Deserialize( + ctx.userblob); + for (const auto &c : commands) { + bool executed = buildcc::env::Command::Execute(c); + if (!executed) { + success = false; + buildcc::env::log_critical(__FUNCTION__, + fmt::format("Failed to run command {}", c)); + break; + } + } + return success; +} + +} // namespace + +namespace buildcc { + +void FileGenerator::AddInput(const std::string &absolute_input_pattern) { + inputs_.emplace(absolute_input_pattern); +} + +void FileGenerator::AddOutput(const std::string &absolute_output_pattern) { + outputs_.emplace(absolute_output_pattern); +} + +void FileGenerator::AddCommand( + const std::string &command_pattern, + const std::unordered_map &arguments) { + std::string constructed_command = ParsePattern(command_pattern, arguments); + commands_.emplace_back(std::move(constructed_command)); +} + +void FileGenerator::Build() { + auto file_blob_handler = + std::make_shared>>( + commands_); + AddIdInfo("Generate", inputs_, outputs_, FileGeneratorGenerateCb, + file_blob_handler); + this->CustomGenerator::Build(); +} + +// PRIVATE + +} // namespace buildcc diff --git a/buildcc/lib/target/src/generator/template_generator.cpp b/buildcc/lib/target/src/generator/template_generator.cpp new file mode 100644 index 00000000..45cab89d --- /dev/null +++ b/buildcc/lib/target/src/generator/template_generator.cpp @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "target/template_generator.h" + +#include "env/env.h" + +namespace { + +bool template_generate_cb(const buildcc::CustomGeneratorContext &ctx) { + std::string pattern_data; + const fs::path &input = *ctx.inputs.begin(); + const fs::path &output = *ctx.outputs.begin(); + + bool success = + buildcc::env::load_file(input.string().c_str(), false, &pattern_data); + if (success) { + std::string parsed_data = ctx.command.Construct(pattern_data); + success = + buildcc::env::save_file(output.string().c_str(), parsed_data, false); + } + + if (!success) { + buildcc::env::log_critical( + __FUNCTION__, fmt::format("Failed to parse {} -> {}", input, output)); + } + return success; +} + +} // namespace + +namespace buildcc { + +void TemplateGenerator::AddTemplate(std::string_view absolute_input_pattern, + std::string_view absolute_output_pattern) { + TemplateInfo info; + info.input_pattern = absolute_input_pattern; + info.output_pattern = absolute_output_pattern; + template_infos_.emplace_back(std::move(info)); +} + +std::string TemplateGenerator::Parse(const std::string &pattern) const { + return ParsePattern(pattern); +} + +/** + * @brief Build FileGenerator Tasks + * + * Use `GetTaskflow` for the registered tasks + */ +void TemplateGenerator::Build() { + for (const auto &info : template_infos_) { + std::string name = string_as_path(ParsePattern(info.input_pattern)) + .lexically_relative(Project::GetRootDir()) + .string(); + AddIdInfo(name, {info.input_pattern}, {info.output_pattern}, + template_generate_cb); + } + this->CustomGenerator::Build(); +} + +} // namespace buildcc diff --git a/buildcc/lib/target/src/target/README.md b/buildcc/lib/target/src/target/README.md new file mode 100644 index 00000000..33955e40 --- /dev/null +++ b/buildcc/lib/target/src/target/README.md @@ -0,0 +1,80 @@ +# Target Segregation + +- [x] `target.cpp` + - Initialization + - Common target specific utility functions + - Assertion functions + - Validity checks +- [x] `base/target_loader.cpp` + - Load from flatbuffer schema file +- [x] `base/target_storer.cpp` + - Store to flatbuffer schema file + +## Target API + +Check the `include/target/api` and `src/api` folder + +- [x] Copy + - Copy src target to dest + - Selective copy using `std::initializer` + - `copy_api` +- [x] Source + - `source_api` +- [x] Header and Include Dir + - `include_api` +- [x] Lib and Lib Dir + - `lib_api` +- [x] PCH + - `pch_api` +- [x] Flags + - `flag_api` + - PreprocessorFlags + - CommonCompileFlags + - AsmCompileFlags + - CCompileFlags + - CppCompileFlag + - LinkFlags +- [ ] Rebuild Deps +- [ ] Getters +- [ ] Target Info + +## Inputs to Target + +- [x] `additional_deps.cpp` + - PreCompileHeader dependencies + - Compile dependencies + - Link dependencies + +## Target states + +- [x] `recheck_states.cpp` + - Add Recheck callback during mock calls + - these callback functions are left blank during in release (only used during unit tests) +- Functions that are mocked during unit tests + - SourceRemoved + - SourceAdded + - SourceUpdated + - PathRemoved + - PathAdded + - PathUpdated + - DirChanged + - FlagChanged + - ExternalLibChanged + +## Target friend + +- [x] file_extension.cpp +- [x] compile_pch.cpp +- [x] compile_object.cpp +- [x] link_target.cpp + +## Action on Target + +- [x] `build.cpp` + - Setup tasks by using the above files + - [ ] PrecompileHeader (pch) files + - [x] Object + - [x] Target +- [x] `tasks.cpp` + - Runs the tasks using Taskflow + - Contains mock equivalent for CppUTest diff --git a/buildcc/lib/target/src/target/build.cpp b/buildcc/lib/target/src/target/build.cpp index 3f241753..18ae8fa4 100644 --- a/buildcc/lib/target/src/target/build.cpp +++ b/buildcc/lib/target/src/target/build.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,36 @@ * limitations under the License. */ -#include "target.h" +#include "target/target.h" -#include "internal/util.h" +#include "target/common/util.h" -#include "assert_fatal.h" +#include "env/assert_fatal.h" #include "fmt/format.h" -namespace buildcc::base { +namespace { + +constexpr const char *const kIncludeDirs = "include_dirs"; +constexpr const char *const kLibDirs = "lib_dirs"; + +constexpr const char *const kPreprocessorFlags = "preprocessor_flags"; +constexpr const char *const kCommonCompileFlags = "common_compile_flags"; +constexpr const char *const kLinkFlags = "link_flags"; + +constexpr const char *const kPchCompileFlags = "pch_compile_flags"; +constexpr const char *const kPchObjectFlags = "pch_object_flags"; +constexpr const char *const kPchObjectOutput = "pch_object_output"; + +constexpr const char *const kAsmCompiler = "asm_compiler"; +constexpr const char *const kCCompiler = "c_compiler"; +constexpr const char *const kCppCompiler = "cpp_compiler"; +constexpr const char *const kArchiver = "archiver"; +constexpr const char *const kLinker = "linker"; + +} // namespace + +namespace buildcc { // * Load // TODO, Verify things that cannot be changed @@ -33,115 +54,76 @@ namespace buildcc::base { void Target::Build() { env::log_trace(name_, __FUNCTION__); - // TODO, Optimize these - aggregated_preprocessor_flags_ = - internal::aggregate(current_preprocessor_flags_); - aggregated_c_compile_flags_ = internal::aggregate(current_c_compile_flags_); - aggregated_cpp_compile_flags_ = - internal::aggregate(current_cpp_compile_flags_); - aggregated_link_flags_ = internal::aggregate(current_link_flags_); - - aggregated_lib_deps_ = - fmt::format("{} {}", internal::aggregate(current_external_lib_deps_), - internal::aggregate(current_lib_deps_)); - - aggregated_include_dirs_ = internal::aggregate_with_prefix( - prefix_include_dir_, current_include_dirs_); - aggregated_lib_dirs_ = - internal::aggregate_with_prefix(prefix_lib_dir_, current_lib_dirs_); - - const bool is_loaded = loader_.Load(); - // TODO, Add more checks for build files physically present - // NOTE, This can go into the recompile logic - if (!is_loaded) { - BuildCompile(); - } else { - BuildRecompile(); + // PCH state + if (!user_.pchs.GetPathInfos().empty()) { + state_.PchDetected(); } - dirty_ = false; -} - -void Target::BuildCompile() { - CompileSources(); - LinkTargetTask(true); - Store(); - first_build_ = true; -} + // Source - Object relation + // Source state + for (const auto &source_info : user_.sources.GetPathInfos()) { + // Set state + state_.SourceDetected(toolchain_.GetConfig().GetFileExt(source_info.path)); -// * Target rebuild depends on -// TODO, Toolchain name -// DONE, Target preprocessor flags -// DONE, Target compile flags -// DONE, Target link flags -// DONE, Target source files -// DONE, Target include dirs -// DONE, Target library dependencies -// TODO, Target library directories -void Target::BuildRecompile() { - - // * Completely compile sources if any of the following change - // TODO, Toolchain, ASM, C, C++ compiler related to a particular name - RecheckFlags(loader_.GetLoadedPreprocessorFlags(), - current_preprocessor_flags_); - RecheckFlags(loader_.GetLoadedCCompileFlags(), current_c_compile_flags_); - RecheckFlags(loader_.GetLoadedCppCompileFlags(), current_cpp_compile_flags_); - RecheckDirs(loader_.GetLoadedIncludeDirs(), current_include_dirs_); - RecheckPaths(loader_.GetLoadedHeaders(), current_header_files_); - - // * Compile sources - if (dirty_) { - CompileSources(); - } else { - RecompileSources(); + // Relate input source with output object + compile_object_.AddObjectData(source_info.path); } - // * Completely rebuild target / link if any of the following change - // Target compiled source files either during Compile / Recompile - // Target library dependencies - RecheckFlags(loader_.GetLoadedLinkFlags(), current_link_flags_); - RecheckDirs(loader_.GetLoadedLibDirs(), current_lib_dirs_); - RecheckPaths(loader_.GetLoadedLibDeps(), current_lib_deps_); - RecheckExternalLib(loader_.GetLoadedExternalLibDeps(), - current_external_lib_deps_); - // TODO, Verify the `physical` presence of the target if dirty_ == false - LinkTargetTask(dirty_); - if (dirty_) { - Store(); - rebuild_ = true; + // Target default arguments + command_.AddDefaultArguments({ + {kIncludeDirs, + internal::aggregate_with_prefix>( + toolchain_.GetConfig().prefix_include_dir, GetIncludeDirs())}, + {kLibDirs, internal::aggregate_with_prefix>( + toolchain_.GetConfig().prefix_lib_dir, GetLibDirs())}, + {kPreprocessorFlags, internal::aggregate(GetPreprocessorFlags())}, + {kCommonCompileFlags, internal::aggregate(GetCommonCompileFlags())}, + // TODO, Cache more flags here + // ASM, C and CPP flags + {kLinkFlags, internal::aggregate(GetLinkFlags())}, + + // Toolchain executables here + {kAsmCompiler, fmt::format("{}", fs::path(toolchain_.GetAssembler()))}, + {kCCompiler, fmt::format("{}", fs::path(toolchain_.GetCCompiler()))}, + {kCppCompiler, fmt::format("{}", fs::path(toolchain_.GetCppCompiler()))}, + {kArchiver, fmt::format("{}", fs::path(toolchain_.GetArchiver()))}, + {kLinker, fmt::format("{}", fs::path(toolchain_.GetLinker()))}, + }); + + // Load the serialized file + (void)serialization_.LoadFromFile(); + + // PCH Compile + if (state_.ContainsPch()) { + command_.AddDefaultArguments({ + {kPchCompileFlags, internal::aggregate(GetPchCompileFlags())}, + {kPchObjectFlags, internal::aggregate(GetPchObjectFlags())}, + {kPchObjectOutput, fmt::format("{}", compile_pch_.GetObjectPath())}, + }); + + compile_pch_.CacheCompileCommand(); + compile_pch_.Task(); + } else { + command_.AddDefaultArguments({ + {kPchCompileFlags, ""}, + {kPchObjectFlags, ""}, + {kPchObjectOutput, ""}, + }); } -} -void Target::LinkTarget() { - // Add compiled sources - const std::string aggregated_compiled_sources = - internal::aggregate(GetCompiledSources()); + // Target State Tasks + EndTask(); - const std::string output_target = - internal::Path::CreateNewPath(GetTargetPath()).GetPathAsString(); + // Compile Command + compile_object_.CacheCompileCommands(); + compile_object_.Task(); - bool success = internal::command( - Link(output_target, aggregated_link_flags_, aggregated_compiled_sources, - aggregated_lib_dirs_, aggregated_lib_deps_)); - env::assert_fatal(success, fmt::format("Compilation failed for: {}", name_)); -} + // Link Command + link_target_.CacheLinkCommand(); + link_target_.Task(); -std::vector -Target::Link(const std::string &output_target, - const std::string &aggregated_link_flags, - const std::string &aggregated_compiled_sources, - const std::string &aggregated_lib_dirs, - const std::string &aggregated_lib_deps) const { - return { - // TODO, Let user decide this during Linking phase - toolchain_.GetCppCompiler(), - aggregated_link_flags, - aggregated_compiled_sources, - "-o", - output_target, - aggregated_lib_dirs, - aggregated_lib_deps, - }; + // Target dependencies + TaskDeps(); } -} // namespace buildcc::base +} // namespace buildcc diff --git a/buildcc/lib/target/src/target/friend/compile_object.cpp b/buildcc/lib/target/src/target/friend/compile_object.cpp new file mode 100644 index 00000000..f905426f --- /dev/null +++ b/buildcc/lib/target/src/target/friend/compile_object.cpp @@ -0,0 +1,269 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "target/friend/compile_object.h" + +#include "target/target.h" + +namespace { + +constexpr const char *const kCompiler = "compiler"; +constexpr const char *const kCompileFlags = "compile_flags"; +constexpr const char *const kOutput = "output"; +constexpr const char *const kInput = "input"; + +} // namespace + +namespace buildcc::internal { + +void CompileObject::AddObjectData(const fs::path &absolute_source_path) { + const fs::path absolute_object_path = + ConstructObjectPath(absolute_source_path); + fs::create_directories(absolute_object_path.parent_path()); + + object_files_.try_emplace( + internal::PathInfo::ToPathString(absolute_source_path), + absolute_object_path, ""); +} + +void CompileObject::CacheCompileCommands() { + for (auto &[absolute_current_source, object_data] : object_files_) { + + const std::string output = + fmt::format("{}", GetObjectData(absolute_current_source).output); + const std::string input = fmt::format("{}", absolute_current_source); + + const auto type = + target_.toolchain_.GetConfig().GetFileExt(absolute_current_source); + const std::string selected_aggregated_compile_flags = + target_.SelectCompileFlags(type).value_or(""); + const std::string selected_compiler = + fmt::format("{}", fs::path(target_.SelectCompiler(type).value_or(""))); + object_data.command = target_.command_.Construct( + target_.GetConfig().compile_command, + { + {kCompiler, selected_compiler}, + {kCompileFlags, selected_aggregated_compile_flags}, + {kOutput, output}, + {kInput, input}, + }); + } +} + +std::vector CompileObject::GetCompiledSources() const { + std::vector compiled_sources; + for (const auto &[_, object_data] : object_files_) { + compiled_sources.push_back(object_data.output); + } + return compiled_sources; +} + +const CompileObject::ObjectData & +CompileObject::GetObjectData(const fs::path &absolute_source) const { + const auto sanitized_source = + internal::PathInfo::ToPathString(absolute_source); + const auto fiter = object_files_.find(sanitized_source); + env::assert_fatal(fiter != object_files_.end(), + fmt::format("{} not found", absolute_source)); + return object_files_.at(sanitized_source); +} + +// PRIVATE + +// NOTE: If RELATIVE TargetEnv supplied +// {target_root_dir} => `Project::GetRootDir()` / +// `target_relative_to_root` +// {target_build_dir} => `Project::GetBuildDir()` / `toolchain.GetName()` +// / `name` + +// Scenarios +// - {target_root_dir} / source -> {target_build_dir} / source.compiled +// - {target_root_dir} / folder / source -> {target_build_dir} / folder / +// source.compiled +// - {target_root_dir} / .. / source -> {target_build_dir} / __ / +// source.compiled -> Warning +// Prompt using TargetEnv(abs_root, abs_build) +// - {target_absolute_root_dir} / FOOLIB / source -> {target_absolute_build_dir} +// / FOOLIB / source + +// TODO, Discuss DifferentOutputFolder API +// {target_root_dir} / random / folder / file.cpp -> {target_build_dir} / random +// / folder / file.cpp.o (SAME) +// {OUT_OF_ROOT_FOLDER} / file.cpp -> {target_build_dir} / {USER_OUTPUT_FOLDER} +// / file.cpp.o +// {OUT_OF_ROOT_FOLDER} / random / folder / file.cpp -> {target_build_dir} / +// {USER_OUTPUT_FOLDER} / random / folder / file.cpp.o +fs::path +CompileObject::ConstructObjectPath(const fs::path &absolute_source_file) const { + + // Compute the relative compiled source path + // Expects to convert + // 1. {project_root_dir} / file.cpp -> file.cpp + // 2. {project_root_dir} / folder / file.cpp -> folder / file.cpp + fs::path relative = + absolute_source_file.lexically_relative(target_.GetTargetRootDir()); + + // Expects to convert + // 1. {project_root_dir} / .. / file.cpp -> .. / file.cpp + // 2. {project_root_dir} / .. / folder / file.cpp -> .. / folder / file.cpp + // - Check if out of root + // - Convert .. to __ + // NOTE, Similar to how CMake handles out of root files + std::string relstr = relative.string(); + if (relstr.find("..") != std::string::npos) { + env::log_warning( + __FUNCTION__, + fmt::format("Out of Root Source detected '{}' -> '{}'. Use " + "TargetEnv to supply absolute target root " + "path -> absolute target build path. By " + "default converts '..' to '__'", + absolute_source_file.string(), relstr)); + std::replace(relstr.begin(), relstr.end(), '.', '_'); + // Converts above + // .. / file.cpp -> __ / file.cpp + // .. / folder / file.cpp -> __ / folder / file.cpp + relative = relstr; + + // TODO, path replacement found + // * API + // AddSourceAbsolute("BUILDCC_HOME / libs / fmt / build.fmt.cpp", + // {"BUILDCC_HOME / libs / fmt", "fmt"}); + + // Converts above + // .. / file.cpp -> {REPLACEMENT_DIR} / file.cpp + // .. / folder / file.cpp -> {REPLACEMENT_DIR} / folder / file.cpp + // relative = relative_replacement_dir / absolute_source_file.filename(); + + // std::string absolute_source_file_str = + // path_as_string(absolute_source_file); + // auto iter = absolute_source_file_str.find(replacement_strategy.first); + // relative = absolute_source_file_str.replace( + // iter, replacement_strategy.first.length(), + // replacement_strategy.second); + } + + // Compute relative object path + fs::path absolute_compiled_source = target_.GetTargetBuildDir() / relative; + absolute_compiled_source.replace_filename( + fmt::format("{}{}", absolute_source_file.filename().string(), + target_.toolchain_.GetConfig().obj_ext)); + return absolute_compiled_source; +} + +void CompileObject::BuildObjectCompile( + std::vector &source_files, + std::vector &dummy_source_files) { + PreObjectCompile(); + + const auto &serialization = target_.serialization_; + const auto &load_target_schema = serialization.GetLoad(); + const auto &user_target_schema = target_.user_; + + if (!serialization.IsLoaded()) { + target_.dirty_ = true; + } else { + if (target_.dirty_) { + } else if (!(load_target_schema.preprocessor_flags == + user_target_schema.preprocessor_flags) || + !(load_target_schema.common_compile_flags == + user_target_schema.common_compile_flags) || + !(load_target_schema.pch_object_flags == + user_target_schema.pch_object_flags) || + !(load_target_schema.asm_compile_flags == + user_target_schema.asm_compile_flags) || + !(load_target_schema.c_compile_flags == + user_target_schema.c_compile_flags) || + !(load_target_schema.cpp_compile_flags == + user_target_schema.cpp_compile_flags)) { + target_.dirty_ = true; + target_.FlagChanged(); + } else if (!(load_target_schema.include_dirs == + user_target_schema.include_dirs)) { + target_.dirty_ = true; + target_.DirChanged(); + } else if (!(load_target_schema.headers == user_target_schema.headers)) { + target_.dirty_ = true; + target_.PathChanged(); + } else if (!(load_target_schema.compile_dependencies == + user_target_schema.compile_dependencies)) { + target_.dirty_ = true; + target_.PathChanged(); + } + } + + if (target_.dirty_) { + CompileSources(source_files); + } else { + RecompileSources(source_files, dummy_source_files); + } +} + +void CompileObject::PreObjectCompile() { + auto &target_user_schema = target_.user_; + + // Convert user_source_files to current_source_files + target_user_schema.sources.ComputeHashForAll(); + + // Convert user_header_files to current_header_files + target_user_schema.headers.ComputeHashForAll(); + + // Convert user_compile_dependencies to current_compile_dependencies + target_user_schema.compile_dependencies.ComputeHashForAll(); +} + +void CompileObject::CompileSources( + std::vector &source_files) { + const auto &target_user_schema = target_.user_; + target_user_schema.sources.GetPathInfos(); + source_files = target_user_schema.sources.GetPathInfos(); +} + +void CompileObject::RecompileSources( + std::vector &source_files, + std::vector &dummy_source_files) { + const auto &serialization = target_.serialization_; + const auto &user_target_schema = target_.user_; + auto previous_source_files = + serialization.GetLoad().sources.GetUnorderedPathInfos(); + + for (const auto ¤t_path_info : + user_target_schema.sources.GetPathInfos()) { + const auto ¤t_path = current_path_info.path; + if (previous_source_files.count(current_path) == 0) { + // Added + source_files.push_back(current_path_info); + target_.dirty_ = true; + target_.SourceAdded(); + } else { + if (!(previous_source_files.at(current_path) == current_path_info.hash)) { + // Updated + source_files.push_back(current_path_info); + target_.dirty_ = true; + target_.SourceUpdated(); + } else { + dummy_source_files.push_back(current_path_info); + } + previous_source_files.erase(current_path); + } + } + + if (!previous_source_files.empty()) { + target_.dirty_ = true; + target_.SourceRemoved(); + } +} + +} // namespace buildcc::internal diff --git a/buildcc/lib/target/src/target/friend/compile_pch.cpp b/buildcc/lib/target/src/target/friend/compile_pch.cpp new file mode 100644 index 00000000..be3b5141 --- /dev/null +++ b/buildcc/lib/target/src/target/friend/compile_pch.cpp @@ -0,0 +1,175 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "target/friend/compile_pch.h" + +#include "schema/path.h" +#include "target/target.h" + +#include "env/util.h" + +namespace { + +constexpr const char *const kCompiler = "compiler"; +constexpr const char *const kCompileFlags = "compile_flags"; +constexpr const char *const kOutput = "output"; +constexpr const char *const kInput = "input"; +constexpr const char *const kInputSource = "input_source"; + +constexpr const char *const kFormat = R"(// Generated by BuildCC +#ifndef BUILDCC_GENERATED_PCH_H_ +#define BUILDCC_GENERATED_PCH_H_ + +// clang-format off +{aggregated_includes} + +#endif +)"; + +void AggregateToFile(const fs::path &filename, + const std::vector &header_files) { + std::string aggregated_includes; + for (const auto &hf : header_files) { + std::string temp = fmt::format("#include \"{}\"\r\n", hf); + aggregated_includes.append(temp); + } + + buildcc::env::Command command; + std::string constructed_output = command.Construct( + kFormat, { + {"aggregated_includes", aggregated_includes}, + }); + bool success = buildcc::env::save_file( + buildcc::path_as_string(filename).c_str(), constructed_output, false); + buildcc::env::assert_fatal(success, "Could not save pch file"); +} + +} // namespace + +namespace buildcc::internal { + +// PUBLIC + +void CompilePch::CacheCompileCommand() { + source_path_ = ConstructSourcePath(target_.GetState().ContainsCpp()); + command_ = ConstructCompileCommand(); +} + +// PRIVATE + +void CompilePch::BuildCompile() { + PreCompile(); + + const auto &serialization = target_.serialization_; + const auto &load_target_schema = serialization.GetLoad(); + const auto &user_target_schema = target_.user_; + + if (!serialization.IsLoaded()) { + target_.dirty_ = true; + } else { + if (!(load_target_schema.preprocessor_flags == + user_target_schema.preprocessor_flags) || + !(load_target_schema.common_compile_flags == + user_target_schema.common_compile_flags) || + !(load_target_schema.pch_compile_flags == + user_target_schema.pch_compile_flags) || + !(load_target_schema.c_compile_flags == + user_target_schema.c_compile_flags) || + !(load_target_schema.cpp_compile_flags == + user_target_schema.cpp_compile_flags)) { + target_.dirty_ = true; + target_.FlagChanged(); + } else if (!(load_target_schema.include_dirs == + user_target_schema.include_dirs)) { + target_.dirty_ = true; + target_.DirChanged(); + } else if (!(load_target_schema.headers == user_target_schema.headers) || + !(load_target_schema.pchs == user_target_schema.pchs)) { + target_.dirty_ = true; + target_.PathChanged(); + } else if (!load_target_schema.pch_compiled) { + // TODO, Replace this with fs::exists to check if compiled pch file is + // present or no + target_.dirty_ = true; + } + } + + if (target_.dirty_) { + AggregateToFile(header_path_, target_.GetPchFiles()); + if (!fs::exists(source_path_)) { + const std::string p = fmt::format("{}", source_path_); + const bool save = + env::save_file(p.c_str(), {"//Generated by BuildCC"}, false); + env::assert_fatal(save, fmt::format("Could not save {}", p)); + } + bool success = env::Command::Execute(command_); + env::assert_fatal(success, "Failed to compile pch"); + } +} + +fs::path CompilePch::ConstructHeaderPath() const { + return target_.GetTargetBuildDir() / + fmt::format("buildcc_pch{}", + target_.toolchain_.GetConfig().pch_header_ext); +} + +fs::path CompilePch::ConstructCompilePath() const { + return ConstructHeaderPath().replace_extension( + fmt::format("{}{}", target_.toolchain_.GetConfig().pch_header_ext, + target_.toolchain_.GetConfig().pch_compile_ext)); +} + +fs::path CompilePch::ConstructSourcePath(bool has_cpp) const { + return ConstructHeaderPath().replace_extension( + fmt::format("{}", has_cpp ? ".cpp" : ".c")); +} + +fs::path CompilePch::ConstructObjectPath() const { + return ConstructHeaderPath().replace_extension( + fmt::format("{}", target_.toolchain_.GetConfig().obj_ext)); +} + +std::string CompilePch::ConstructCompileCommand() const { + std::string compiler = target_.GetState().ContainsCpp() + ? target_.GetToolchain().GetCppCompiler() + : target_.GetToolchain().GetCCompiler(); + compiler = fmt::format("{}", fs::path(compiler)); + const FileExt file_ext_type = + target_.GetState().ContainsCpp() ? FileExt::Cpp : FileExt::C; + const std::string compile_flags = + target_.SelectCompileFlags(file_ext_type).value_or(""); + const std::string pch_compile_path = fmt::format("{}", compile_path_); + const std::string pch_header_path = fmt::format("{}", header_path_); + const std::string pch_source_path = fmt::format("{}", source_path_); + return target_.command_.Construct(target_.GetConfig().pch_command, + { + {kCompiler, compiler}, + {kCompileFlags, compile_flags}, + {kOutput, pch_compile_path}, + {kInput, pch_header_path}, + {kInputSource, pch_source_path}, + }); +} + +void CompilePch::PreCompile() { + auto &target_user_schema = target_.user_; + + target_user_schema.headers.ComputeHashForAll(); + + target_user_schema.pchs.ComputeHashForAll(); +} + +} // namespace buildcc::internal diff --git a/buildcc/lib/target/src/target/friend/link_target.cpp b/buildcc/lib/target/src/target/friend/link_target.cpp new file mode 100644 index 00000000..fb5219ab --- /dev/null +++ b/buildcc/lib/target/src/target/friend/link_target.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "target/friend/link_target.h" + +#include "target/target.h" + +namespace { +constexpr const char *const kOutput = "output"; +constexpr const char *const kCompiledSources = "compiled_sources"; +constexpr const char *const kLibDeps = "lib_deps"; +} // namespace + +namespace buildcc::internal { + +// PUBLIC + +void LinkTarget::CacheLinkCommand() { + // Add compiled sources + const std::string aggregated_compiled_sources = + internal::aggregate(target_.compile_object_.GetCompiledSources()); + + const std::string output_target = fmt::format("{}", output_); + const auto &target_user_schema = target_.user_; + command_ = target_.command_.Construct( + target_.GetConfig().link_command, + { + {kOutput, output_target}, + {kCompiledSources, aggregated_compiled_sources}, + {kLibDeps, + fmt::format("{} {}", + internal::aggregate(target_user_schema.libs.GetPaths()), + internal::aggregate(target_user_schema.external_libs))}, + }); +} + +// PRIVATE + +fs::path LinkTarget::ConstructOutputPath() const { + fs::path path = + target_.GetTargetBuildDir() / + fmt::format("{}{}", target_.GetName(), target_.GetConfig().target_ext); + path.make_preferred(); + return path; +} + +void LinkTarget::PreLink() { + auto &target_user_schema = target_.user_; + + target_user_schema.libs.ComputeHashForAll(); + + target_user_schema.link_dependencies.ComputeHashForAll(); +} + +void LinkTarget::BuildLink() { + PreLink(); + + const auto &serialization = target_.serialization_; + const auto &target_load_schema = serialization.GetLoad(); + const auto &target_user_schema = target_.user_; + + if (!serialization.IsLoaded()) { + target_.dirty_ = true; + } else { + if (target_.dirty_) { + // Skip all the other else if checks + } else if (!(target_load_schema.link_flags == + target_user_schema.link_flags)) { + target_.dirty_ = true; + target_.FlagChanged(); + } else if (!(target_load_schema.lib_dirs == target_user_schema.lib_dirs)) { + target_.dirty_ = true; + target_.DirChanged(); + } else if (!(target_load_schema.external_libs == + target_user_schema.external_libs)) { + target_.dirty_ = true; + target_.ExternalLibChanged(); + } else if (!(target_load_schema.link_dependencies == + target_user_schema.link_dependencies) || + !(target_load_schema.libs == target_user_schema.libs)) { + target_.dirty_ = true; + target_.PathChanged(); + } else if (!target_load_schema.target_linked) { + // TODO, Replace this with fs::exists to check if linked target is present + // or no + target_.dirty_ = true; + } + } + + if (target_.dirty_) { + bool success = env::Command::Execute(command_); + env::assert_fatal(success, "Failed to link target"); + target_.serialization_.UpdateTargetCompiled(); + } +} + +} // namespace buildcc::internal diff --git a/buildcc/lib/target/src/target/include_dir.cpp b/buildcc/lib/target/src/target/include_dir.cpp deleted file mode 100644 index 9365b970..00000000 --- a/buildcc/lib/target/src/target/include_dir.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "target.h" - -#include "assert_fatal.h" -#include "logging.h" - -#include "internal/util.h" - -#include "fmt/format.h" - -namespace buildcc::base { - -void Target::AddHeaderAbsolute(const fs::path &absolute_filepath) { - env::assert_fatal(IsValidHeader(absolute_filepath), - fmt::format("{} does not have a valid header extension", - absolute_filepath.string())); - internal::add_path(absolute_filepath, current_header_files_); -} - -void Target::AddHeader(const std::string &relative_filename, - const fs::path &relative_to_target_path) { - env::log_trace(name_, __FUNCTION__); - - // Check Source - fs::path absolute_filepath = - target_root_source_dir_ / relative_to_target_path / relative_filename; - AddHeaderAbsolute(absolute_filepath); -} - -void Target::AddHeader(const std::string &relative_filename) { - AddHeader(relative_filename, ""); -} - -void Target::GlobHeaders(const fs::path &relative_to_target_path) { - env::log_trace(name_, __FUNCTION__); - - fs::path absolute_path = target_root_source_dir_ / relative_to_target_path; - GlobHeadersAbsolute(absolute_path); -} - -void Target::GlobHeadersAbsolute(const fs::path &absolute_path) { - for (const auto &p : fs::directory_iterator(absolute_path)) { - if (IsValidHeader(p.path())) { - env::log_trace(name_, fmt::format("Added header {}", p.path().string())); - AddHeaderAbsolute(p.path()); - } - } -} - -// Public -void Target::AddIncludeDir(const fs::path &relative_include_dir, - bool glob_headers) { - env::log_trace(name_, __FUNCTION__); - - const fs::path absolute_include_dir = - (target_root_source_dir_ / relative_include_dir); - AddIncludeDirAbsolute(absolute_include_dir, glob_headers); -} - -void Target::AddIncludeDirAbsolute(const fs::path &absolute_include_dir, - bool glob_headers) { - const auto include_path = internal::Path::CreateNewPath(absolute_include_dir); - current_include_dirs_.insert(include_path.GetPathAsString()); - - if (glob_headers) { - GlobHeadersAbsolute(include_path.GetPathname()); - } -} - -} // namespace buildcc::base diff --git a/buildcc/lib/target/src/target/lib.cpp b/buildcc/lib/target/src/target/lib.cpp deleted file mode 100644 index 4feabe7d..00000000 --- a/buildcc/lib/target/src/target/lib.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "target.h" - -#include "assert_fatal.h" - -#include "internal/util.h" - -namespace buildcc::base { - -void Target::AddLibDir(const fs::path &relative_lib_dir) { - env::log_trace(name_, __FUNCTION__); - - fs::path final_lib_dir = target_root_source_dir_ / relative_lib_dir; - AddLibDirAbsolute(final_lib_dir); -} - -void Target::AddLibDirAbsolute(const fs::path &absolute_lib_dir) { - env::log_trace(name_, __FUNCTION__); - - current_lib_dirs_.insert( - internal::Path::CreateNewPath(absolute_lib_dir).GetPathAsString()); -} - -void Target::AddLibDep(const Target &lib_dep) { - env::log_trace(name_, __FUNCTION__); - - const fs::path lib_dep_path = lib_dep.GetTargetPath(); - internal::add_path(lib_dep_path, current_lib_deps_); -} - -void Target::AddLibDep(const std::string &lib_dep) { - env::log_trace(name_, __FUNCTION__); - current_external_lib_deps_.insert(lib_dep); -} - -} // namespace buildcc::base diff --git a/buildcc/lib/target/src/target/recheck_states.cpp b/buildcc/lib/target/src/target/recheck_states.cpp index d84f0a1b..4285fe0f 100644 --- a/buildcc/lib/target/src/target/recheck_states.cpp +++ b/buildcc/lib/target/src/target/recheck_states.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ // NOTE, This source file is only present so that `mock/recheck_states.cpp` can // be used accurately -#include "target.h" +#include "target/target.h" -namespace buildcc::base { +namespace buildcc { // Source rechecks void Target::SourceRemoved() {} @@ -30,8 +30,9 @@ void Target::PathRemoved() {} void Target::PathAdded() {} void Target::PathUpdated() {} +void Target::PathChanged() {} void Target::DirChanged() {} void Target::FlagChanged() {} void Target::ExternalLibChanged() {} -} // namespace buildcc::base +} // namespace buildcc diff --git a/buildcc/lib/target/src/target/source.cpp b/buildcc/lib/target/src/target/source.cpp deleted file mode 100644 index fa50954a..00000000 --- a/buildcc/lib/target/src/target/source.cpp +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "target.h" - -#include "internal/util.h" - -#include "assert_fatal.h" - -#include "fmt/format.h" - -namespace buildcc::base { - -// Public -void Target::AddSourceAbsolute(const fs::path &absolute_input_filepath, - const fs::path &absolute_output_filepath) { - env::assert_fatal(IsValidSource(absolute_input_filepath), - fmt::format("{} does not have a valid source extension", - absolute_input_filepath.string())); - - const fs::path absolute_source = - fs::path(absolute_input_filepath).make_preferred(); - internal::add_path(absolute_source, current_source_files_); - - // Relate input source files with output object files - const auto absolute_compiled_source = - internal::Path::CreateNewPath(absolute_output_filepath); - fs::create_directories(absolute_compiled_source.GetPathname().parent_path()); - current_object_files_.insert( - {absolute_source.native(), absolute_compiled_source}); -} - -void Target::GlobSourcesAbsolute(const fs::path &absolute_input_path, - const fs::path &absolute_output_path) { - for (const auto &p : fs::directory_iterator(absolute_input_path)) { - if (IsValidSource(p.path())) { - fs::path absolute_output_source = - absolute_output_path / (p.path().filename().string() + ".o"); - AddSourceAbsolute(p.path(), absolute_output_source); - } - } -} - -void Target::AddSource(const fs::path &relative_filename, - const std::filesystem::path &relative_to_target_path) { - env::log_trace(name_, __FUNCTION__); - - // Compute the absolute source path - fs::path absolute_source = - target_root_source_dir_ / relative_to_target_path / relative_filename; - absolute_source.make_preferred(); - - // Compute the relative compiled source path - fs::path relative = - absolute_source.lexically_relative(env::get_project_root_dir()); - - // - Check if out of root - // - Convert .. to __ - // NOTE, Similar to how CMake handles out of root files - std::string relstr = relative.string(); - if (relstr.find("..") != std::string::npos) { - // TODO, If unnecessary, remove this warning - env::log_warning( - name_, - fmt::format("Out of project root path detected for {} -> " - "{}.\nConverting .. to __ but it is recommended to use the " - "AddSourceAbsolute(abs_source_input, abs_obj_output) or " - "GlobSourceAbsolute(abs_source_input, abs_obj_output) " - "API if possible.", - absolute_source.string(), relative.string())); - std::replace(relstr.begin(), relstr.end(), '.', '_'); - relative = relstr; - } - - // Compute relative object path - fs::path absolute_compiled_source = target_intermediate_dir_ / relative; - absolute_compiled_source.replace_filename( - absolute_source.filename().string() + ".o"); - - AddSourceAbsolute(absolute_source, absolute_compiled_source); -} - -void Target::GlobSources(const fs::path &relative_to_target_path) { - env::log_trace(name_, __FUNCTION__); - - fs::path absolute_input_path = - target_root_source_dir_ / relative_to_target_path; - - for (const auto &p : fs::directory_iterator(absolute_input_path)) { - if (IsValidSource(p.path())) { - AddSource(p.path().lexically_relative(target_root_source_dir_)); - } - } -} - -// Aliases -void Target::AddSource(const fs::path &relative_filename) { - AddSource(relative_filename, ""); -} - -// Private - -void Target::CompileSources() { - env::log_trace(name_, __FUNCTION__); - std::vector compile_sources; - std::transform(current_source_files_.begin(), current_source_files_.end(), - std::back_inserter(compile_sources), - [](const buildcc::internal::Path &p) -> fs::path { - return p.GetPathname(); - }); - CompileTargetTask(std::move(compile_sources), std::vector()); -} - -void Target::RecompileSources() { - env::log_trace(name_, __FUNCTION__); - - const auto &previous_source_files = loader_.GetLoadedSources(); - - // * Cannot find previous source in current source files - const bool is_source_removed = internal::is_previous_paths_different( - previous_source_files, current_source_files_); - if (is_source_removed) { - dirty_ = true; - SourceRemoved(); - } - - std::vector compile_sources; - std::vector dummy_compile_sources; - for (const auto ¤t_file : current_source_files_) { - const auto ¤t_source = current_file.GetPathname(); - - // Find current_file in the loaded sources - auto iter = previous_source_files.find(current_file); - - if (iter == previous_source_files.end()) { - // *1 New source file added to build - compile_sources.push_back(current_source); - dirty_ = true; - SourceAdded(); - } else { - // *2 Current file is updated - if (current_file.GetLastWriteTimestamp() > - iter->GetLastWriteTimestamp()) { - compile_sources.push_back(current_source); - dirty_ = true; - SourceUpdated(); - } else { - // TODO, Verify the `physical` presence of object file - - // ELSE - // *3 Do nothing - dummy_compile_sources.push_back(current_source); - } - } - } - - CompileTargetTask(std::move(compile_sources), - std::move(dummy_compile_sources)); -} - -void Target::CompileSource(const fs::path ¤t_source) const { - const bool success = internal::command(CompileCommand(current_source)); - env::assert_fatal(success, fmt::format("Compilation failed for: {}", - current_source.string())); -} - -std::vector -Target::CompileCommand(const fs::path ¤t_source) const { - const std::string output_source = - GetCompiledSourcePath(current_source).GetPathAsString(); - - // TODO, Check implementation for GetCompiler - const std::string compiler = GetCompiler(current_source); - - // TODO, This doesn't look clean - const auto type = GetFileExtType(current_source); - const std::string &aggregated_compile_flags = - type == FileExtType::C ? aggregated_c_compile_flags_ - : type == FileExtType::Cpp ? aggregated_cpp_compile_flags_ - : ""; - - const std::string input_source = - internal::Path::CreateExistingPath(current_source).GetPathAsString(); - return CompileCommand(input_source, output_source, compiler, - aggregated_preprocessor_flags_, - aggregated_compile_flags, aggregated_include_dirs_); -} - -std::vector -Target::CompileCommand(const std::string &input_source, - const std::string &output_source, - const std::string &compiler, - const std::string &aggregated_preprocessor_flags, - const std::string &aggregated_compile_flags, - const std::string &aggregated_include_dirs) const { - return { - compiler, - aggregated_preprocessor_flags, - aggregated_include_dirs, - aggregated_compile_flags, - "-o", - output_source, - "-c", - input_source, - }; -} - -} // namespace buildcc::base diff --git a/buildcc/lib/target/src/target/target.cpp b/buildcc/lib/target/src/target/target.cpp index cfeca733..48481daf 100644 --- a/buildcc/lib/target/src/target/target.cpp +++ b/buildcc/lib/target/src/target/target.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,13 @@ * limitations under the License. */ -#include "target.h" +#include "target/target.h" // Internal -#include "internal/util.h" +#include "target/common/util.h" // Env -#include "assert_fatal.h" +#include "env/assert_fatal.h" // Fmt #include "fmt/format.h" @@ -29,11 +29,11 @@ namespace fs = std::filesystem; namespace { -bool IsValidTargetType(buildcc::base::TargetType type) { +bool IsValidTargetType(buildcc::TargetType type) { switch (type) { - case buildcc::base::TargetType::Executable: - case buildcc::base::TargetType::StaticLibrary: - case buildcc::base::TargetType::DynamicLibrary: + case buildcc::TargetType::Executable: + case buildcc::TargetType::StaticLibrary: + case buildcc::TargetType::DynamicLibrary: return true; break; default: @@ -44,187 +44,55 @@ bool IsValidTargetType(buildcc::base::TargetType type) { } // namespace -namespace buildcc::base { +namespace buildcc { -// PROTECTED - -// Getters - -FileExtType Target::GetFileExtType(const fs::path &filepath) const { - if (!filepath.has_extension()) { - return FileExtType::Invalid; - } - - FileExtType type = FileExtType::Invalid; - const std::string ext = filepath.extension().string(); - - if (valid_c_ext_.count(ext) == 1) { - type = FileExtType::C; - } else if (valid_cpp_ext_.count(ext) == 1) { - type = FileExtType::Cpp; - } else if (valid_asm_ext_.count(ext) == 1) { - type = FileExtType::Asm; - } else if (valid_header_ext_.count(ext) == 1) { - type = FileExtType::Header; - } +void Target::Initialize() { + // Checks + env::assert_fatal( + Project::IsInit(), + "Environment is not initialized. Use the buildcc::Project::Init API"); + env::assert_fatal(IsValidTargetType(type_), "Invalid Target Type"); + fs::create_directories(GetTargetBuildDir()); - return type; + // String updates + unique_id_ = fmt::format("[{}] {}", toolchain_.GetName(), name_); + std::string path = fmt::format( + "{}", GetTargetPath().lexically_relative(Project::GetBuildDir())); + tf_.name(path); } -bool Target::IsValidSource(const fs::path &sourcepath) const { - bool valid = false; - switch (GetFileExtType(sourcepath)) { - case FileExtType::Asm: - case FileExtType::C: - case FileExtType::Cpp: - valid = true; +env::optional Target::SelectCompileFlags(FileExt ext) const { + switch (ext) { + case FileExt::Asm: + return internal::aggregate(GetAsmCompileFlags()); break; - case FileExtType::Header: - default: - valid = false; + case FileExt::C: + return internal::aggregate(GetCCompileFlags()); break; - } - return valid; -} - -bool Target::IsValidHeader(const fs::path &headerpath) const { - bool valid = false; - switch (GetFileExtType(headerpath)) { - case FileExtType::Header: - valid = true; + case FileExt::Cpp: + return internal::aggregate(GetCppCompileFlags()); break; - case FileExtType::Asm: - case FileExtType::C: - case FileExtType::Cpp: default: - valid = false; break; } - return valid; + return {}; } -// TODO, Since we are sanitizing the input source files when adding we might -// only need to check for valid sources (ASM, C, CPP) -// i.e we can eliminate the default case -const std::string &Target::GetCompiler(const fs::path &source) const { - switch (GetFileExtType(source)) { - case FileExtType::Asm: - return toolchain_.GetAsmCompiler(); +env::optional Target::SelectCompiler(FileExt ext) const { + switch (ext) { + case FileExt::Asm: + return GetToolchain().GetAssembler(); break; - case FileExtType::C: - return toolchain_.GetCCompiler(); + case FileExt::C: + return GetToolchain().GetCCompiler(); break; - case FileExtType::Cpp: + case FileExt::Cpp: + return GetToolchain().GetCppCompiler(); break; default: - buildcc::env::assert_fatal( - false, fmt::format("Invalid source {}", source.string())); break; } - return toolchain_.GetCppCompiler(); -} - -const internal::Path & -Target::GetCompiledSourcePath(const fs::path &source) const { - const auto fiter = current_object_files_.find(source.native()); - env::assert_fatal(fiter != current_object_files_.end(), - fmt::format("{} not found", source.string())); - return current_object_files_.at(source.native()); -} - -internal::path_unordered_set Target::GetCompiledSources() const { - internal::path_unordered_set compiled_sources; - for (const auto &p : current_object_files_) { - compiled_sources.insert(p.second); - } - return compiled_sources; -} - -// PRIVATE - -void Target::Initialize() { - // Checks - env::assert_fatal( - env::is_init(), - "Environment is not initialized. Use the buildcc::env::init API"); - env::assert_fatal(IsValidTargetType(type_), "Invalid Target Type"); - fs::create_directories(target_intermediate_dir_); - - // Taskflow parameters - tf_.name(name_); -} - -// Rechecks -void Target::RecheckPaths(const internal::path_unordered_set &previous_path, - const internal::path_unordered_set ¤t_path) { - // * Compile sources / Target already requires rebuild - if (dirty_) { - return; - } - - // * Old path is removed - const bool removed = - internal::is_previous_paths_different(previous_path, current_path); - if (removed) { - PathRemoved(); - dirty_ = true; - return; - } - - for (const auto &path : current_path) { - auto iter = previous_path.find(path); - - if (iter == previous_path.end()) { - // * New path added - PathAdded(); - dirty_ = true; - } else { - // * Path is updated - if (path.GetLastWriteTimestamp() > iter->GetLastWriteTimestamp()) { - PathUpdated(); - dirty_ = true; - } else { - // * Do nothing - } - } - - if (dirty_) { - break; - } - } -} - -void Target::RecheckDirs(const std::unordered_set &previous_dirs, - const std::unordered_set ¤t_dirs) { - RecheckChanged(previous_dirs, current_dirs, - std::bind(&Target::DirChanged, this)); -} - -void Target::RecheckFlags( - const std::unordered_set &previous_flags, - const std::unordered_set ¤t_flags) { - RecheckChanged(previous_flags, current_flags, - std::bind(&Target::FlagChanged, this)); -} - -void Target::RecheckExternalLib( - const std::unordered_set &previous_external_libs, - const std::unordered_set ¤t_external_libs) { - RecheckChanged(previous_external_libs, current_external_libs, - std::bind(&Target::ExternalLibChanged, this)); -} - -void Target::RecheckChanged(const std::unordered_set &previous, - const std::unordered_set ¤t, - std::function callback) { - if (dirty_) { - return; - } - - if (previous != current) { - callback(); - dirty_ = true; - } + return {}; } -} // namespace buildcc::base +} // namespace buildcc diff --git a/buildcc/lib/target/src/target/tasks.cpp b/buildcc/lib/target/src/target/tasks.cpp index dd0f3cf5..ef9f3e04 100644 --- a/buildcc/lib/target/src/target/tasks.cpp +++ b/buildcc/lib/target/src/target/tasks.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,51 +14,161 @@ * limitations under the License. */ -#include "target.h" +#include "target/target.h" -#include "assert_fatal.h" -#include "logging.h" +#include -#include "internal/util.h" +#include "env/logging.h" + +#include "target/common/util.h" #include "fmt/format.h" -namespace buildcc::base { - -void Target::CompileTargetTask( - const std::vector &&compile_sources, - const std::vector &&dummy_compile_sources) { - compile_task_ = - tf_.emplace([this, compile_sources, - dummy_compile_sources](tf::Subflow &subflow) { - for (const auto &cs : compile_sources) { - std::string name = - cs.lexically_relative(env::get_project_root_dir()).string(); - (void)subflow.emplace([this, cs]() { CompileSource(cs); }) - .name(name); - } - - // NOTE, This has just been added for graph generation - for (const auto &dcs : dummy_compile_sources) { - std::string name = - dcs.lexically_relative(env::get_project_root_dir()).string(); - (void)subflow.emplace([]() {}).name(name); - } - }) - .name(kCompileTaskName); +namespace { + +constexpr const char *const kStartTaskName = "Start Target"; +constexpr const char *const kEndTaskName = "End Target"; +constexpr const char *const kCheckTaskName = "Check Target"; + +constexpr const char *const kPchTaskName = "Pch"; +constexpr const char *const kCompileTaskName = "Objects"; +constexpr const char *const kLinkTaskName = "Target"; + +} // namespace + +namespace buildcc {} // namespace buildcc + +namespace buildcc::internal { + +// 1. User adds/removes/updates pch_headers +// 2. `BuildCompile` aggregates pch_headers to a single `buildcc_header` and +// compiles +// 3. Successfully compiled sources are added to `compiled_pch_files_` +void CompilePch::Task() { + task_ = target_.tf_.emplace([&](tf::Subflow &subflow) { + if (env::get_task_state() != env::TaskState::SUCCESS) { + return; + } + + try { + BuildCompile(); + target_.serialization_.UpdatePchCompiled(target_.user_); + } catch (...) { + env::set_task_state(env::TaskState::FAILURE); + } + + // For Graph generation + for (const auto &p : target_.GetPchFiles()) { + std::string name = fmt::format( + "{}", fs::path(p).lexically_relative(Project::GetRootDir())); + subflow.placeholder().name(name); + } + }); + task_.name(kPchTaskName); } -void Target::LinkTargetTask(const bool link) { - env::log_trace(name_, __FUNCTION__); +// 1. User adds/removes/updates sources (user_source_files) +// 2. `BuildObjectCompile` populates `selected_source_files` that need to be +// compiled +// 3. Successfully compiled sources are added to `compiled_source_files_` +// * `selected_source_files` can be a subset of `user_source_files` +// * `compiled_source_files` can be a subset of `selected_source_files` +// NOTE, We do not use state here since we are compiling every source file +// individually +// We would need to store `source_file : object_file : state` in our +// serialization schema +void CompileObject::Task() { + compile_task_ = target_.tf_.emplace([&](tf::Subflow &subflow) { + if (env::get_task_state() != env::TaskState::SUCCESS) { + return; + } - if (link) { - link_task_ = tf_.emplace([this]() { LinkTarget(); }); - } else { - link_task_ = tf_.emplace([]() {}); - } + std::vector selected_source_files; + std::vector selected_dummy_source_files; + + try { + BuildObjectCompile(selected_source_files, selected_dummy_source_files); + for (const auto &path_info : selected_dummy_source_files) { + target_.serialization_.AddSource(path_info.path, path_info.hash); + } - link_task_.name(kLinkTaskName); - link_task_.succeed(compile_task_); + for (const auto &path_info : selected_source_files) { + std::string name = fmt::format( + "{}", + fs::path(path_info.path).lexically_relative(Project::GetRootDir())); + (void)subflow + .emplace([this, path_info]() { + try { + bool success = env::Command::Execute( + GetObjectData(path_info.path).command); + env::assert_fatal(success, "Could not compile source"); + target_.serialization_.AddSource(path_info.path, + path_info.hash); + } catch (...) { + env::set_task_state(env::TaskState::FAILURE); + } + }) + .name(name); + } + + // For graph generation + for (const auto &dummy_path_info : selected_dummy_source_files) { + std::string name = + fmt::format("{}", fs::path(dummy_path_info.path) + .lexically_relative(Project::GetRootDir())); + (void)subflow.placeholder().name(name); + } + } catch (...) { + env::set_task_state(env::TaskState::FAILURE); + } + }); + compile_task_.name(kCompileTaskName); +} + +// 1. Receives object list from compile stage (not serialized) +// 2. `BuildLink` links compiled objects and other user supplied parameters to +// the required target +// 3. Successfully linking the target sets link state +void LinkTarget::Task() { + task_ = target_.tf_.emplace([&]() { + if (env::get_task_state() != env::TaskState::SUCCESS) { + return; + } + try { + BuildLink(); + } catch (...) { + env::set_task_state(env::TaskState::FAILURE); + } + }); + task_.name(kLinkTaskName); +} + +} // namespace buildcc::internal + +namespace buildcc { + +void Target::EndTask() { + target_end_task_ = tf_.emplace([&]() { + if (dirty_) { + try { + serialization_.UpdateStore(user_); + env::assert_fatal(serialization_.StoreToFile(), + fmt::format("Store failed for {}", GetName())); + state_.BuildCompleted(); + } catch (...) { + env::set_task_state(env::TaskState::FAILURE); + } + } + }); + target_end_task_.name(kEndTaskName); +} + +void Target::TaskDeps() { + if (state_.ContainsPch()) { + compile_pch_.GetTask().precede(compile_object_.GetTask()); + } + compile_object_.GetTask().precede(link_target_.GetTask()); + link_target_.GetTask().precede(target_end_task_); } -} // namespace buildcc::base +} // namespace buildcc diff --git a/buildcc/lib/target/src/target_info/target_info.cpp b/buildcc/lib/target/src/target_info/target_info.cpp new file mode 100644 index 00000000..2fc83cce --- /dev/null +++ b/buildcc/lib/target/src/target_info/target_info.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "target/target_info.h" + +namespace buildcc { + +// PRIVATE + +void TargetInfo::Initialize() { + std::for_each(toolchain_.GetPreprocessorFlags().begin(), + toolchain_.GetPreprocessorFlags().end(), + [&](const std::string &flag) { AddPreprocessorFlag(flag); }); + std::for_each(toolchain_.GetCommonCompileFlags().begin(), + toolchain_.GetCommonCompileFlags().end(), + [&](const std::string &flag) { AddCommonCompileFlag(flag); }); + std::for_each(toolchain_.GetPchCompileFlags().begin(), + toolchain_.GetPchCompileFlags().end(), + [&](const std::string &flag) { AddPchCompileFlag(flag); }); + std::for_each(toolchain_.GetPchObjectFlags().begin(), + toolchain_.GetPchObjectFlags().end(), + [&](const std::string &flag) { AddPchObjectFlag(flag); }); + std::for_each(toolchain_.GetAsmCompileFlags().begin(), + toolchain_.GetAsmCompileFlags().end(), + [&](const std::string &flag) { AddAsmCompileFlag(flag); }); + std::for_each(toolchain_.GetCCompileFlags().begin(), + toolchain_.GetCCompileFlags().end(), + [&](const std::string &flag) { AddCCompileFlag(flag); }); + std::for_each(toolchain_.GetCppCompileFlags().begin(), + toolchain_.GetCppCompileFlags().end(), + [&](const std::string &flag) { AddCppCompileFlag(flag); }); + std::for_each(toolchain_.GetLinkFlags().begin(), + toolchain_.GetLinkFlags().end(), + [&](const std::string &flag) { AddLinkFlag(flag); }); +} + +} // namespace buildcc diff --git a/buildcc/lib/target/src/util/util.cpp b/buildcc/lib/target/src/util/util.cpp deleted file mode 100644 index 44bfc601..00000000 --- a/buildcc/lib/target/src/util/util.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "internal/util.h" - -#include "assert_fatal.h" -#include "env.h" - -#include "fmt/format.h" - -namespace buildcc::internal { - -// rechecks -bool is_previous_paths_different(const path_unordered_set &previous_paths, - const path_unordered_set ¤t_paths) { - return std::any_of(previous_paths.begin(), previous_paths.end(), - [&](const internal::Path &p) { - return current_paths.find(p) == current_paths.end(); - }); -} - -// Additions -bool add_path(const fs::path &path, path_unordered_set &stored_paths) { - auto current_file = buildcc::internal::Path::CreateExistingPath(path); - - // TODO, Note, we might not require this check - env::assert_fatal(stored_paths.find(current_file) == stored_paths.end(), - fmt::format("{} duplicate found", path.string())); - - auto [_, added] = stored_paths.insert(current_file); - return added; -} - -// Aggregates - -std::string aggregate(const std::vector &list) { - return fmt::format("{}", fmt::join(list, " ")); -} - -std::string aggregate(const std::unordered_set &list) { - return fmt::format("{}", fmt::join(list, " ")); -} - -std::string aggregate(const buildcc::internal::path_unordered_set &paths) { - std::vector agg; - std::transform(paths.begin(), paths.end(), std::back_inserter(agg), - [](const buildcc::internal::Path &p) -> std::string { - return p.GetPathAsString(); - }); - return aggregate(agg); -} - -std::string aggregate_with_prefix(const std::string &prefix, - const std::unordered_set &dirs) { - std::vector agg; - std::transform(dirs.begin(), dirs.end(), std::back_inserter(agg), - [&](const std::string &dir) -> std::string { - return fmt::format("{}{}", prefix, dir); - }); - return aggregate(agg); -} - -} // namespace buildcc::internal diff --git a/buildcc/lib/target/test/path/CMakeLists.txt b/buildcc/lib/target/test/path/CMakeLists.txt deleted file mode 100644 index 1b5103ce..00000000 --- a/buildcc/lib/target/test/path/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -configure_file(constants.h.in ${CMAKE_CURRENT_BINARY_DIR}/generated/constants.h @ONLY) - -set(TEST_NAME "test_path") -add_executable( - ${TEST_NAME} - ${TEST_NAME}.cpp -) -target_include_directories(${TEST_NAME} PRIVATE - ${TARGET_DIR}/include - ${CMAKE_CURRENT_BINARY_DIR}/generated -) -target_link_libraries(${TEST_NAME} PRIVATE - mock_env - CppUTest - CppUTestExt - gcov -) -target_compile_options(${TEST_NAME} PRIVATE ${TEST_COMPILE_FLAGS}) -target_link_options(${TEST_NAME} PRIVATE ${TEST_LINK_FLAGS}) - -add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) diff --git a/buildcc/lib/target/test/path/constants.h.in b/buildcc/lib/target/test/path/constants.h.in deleted file mode 100644 index 9da788ce..00000000 --- a/buildcc/lib/target/test/path/constants.h.in +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -// clang-format off -inline constexpr char const *BUILD_SCRIPT_SOURCE = "@CMAKE_CURRENT_SOURCE_DIR@"; diff --git a/buildcc/lib/target/test/path/test_path.cpp b/buildcc/lib/target/test/path/test_path.cpp deleted file mode 100644 index 1f471bc2..00000000 --- a/buildcc/lib/target/test/path/test_path.cpp +++ /dev/null @@ -1,111 +0,0 @@ -// Internal -#include "internal/path.h" - -#include "assert_fatal.h" - -#include "constants.h" - -#include -#include - -// NOTE, Make sure all these includes are AFTER the system and header includes -#include "CppUTest/CommandLineTestRunner.h" -#include "CppUTest/MemoryLeakDetectorNewMacros.h" -#include "CppUTest/TestHarness.h" -#include "CppUTest/Utest.h" - -// clang-format off -TEST_GROUP(PathTestGroup) -{ -}; -// clang-format on - -static const auto current_file_path = - (fs::path(BUILD_SCRIPT_SOURCE) / "path_main.cpp").make_preferred(); - -TEST(PathTestGroup, Path_ExistingPathStaticConstructor) { - auto existing_path = - buildcc::internal::Path::CreateExistingPath(current_file_path); - STRCMP_EQUAL(existing_path.GetPathname().string().c_str(), - current_file_path.string().c_str()); - // * NOTE, Last write timestamp changes whenever we resave or re-download - // This would not work well with Git - // UNSIGNED_LONGLONGS_EQUAL(existing_path.GetLastWriteTimestamp(), - // 13623997187709551616ULL); -} - -TEST(PathTestGroup, Path_ExistingPathStaticConstructor_ThrowFileException) { - CHECK_THROWS( - buildcc::env::assert_exception, - buildcc::internal::Path::CreateExistingPath("random_path_main.cpp")); - - try { - auto existing_filename = - buildcc::internal::Path::CreateExistingPath("random_path_main.cpp"); - } catch (const buildcc::env::assert_exception &e) { - // * NOTE, We cannot check this since different compilers might - // have different error messages - // STRCMP_EQUAL( - // e.what(), - // "filesystem error: cannot get file time: No such file or directory " - // "[random_path_main.cpp]"); - } -} - -TEST(PathTestGroup, PathConstructor_NewPathStaticConstructor) { - buildcc::internal::Path p = - buildcc::internal::Path::CreateNewPath("random_path_main.cpp", 12345ULL); - STRCMP_EQUAL(p.GetPathname().string().c_str(), "random_path_main.cpp"); - UNSIGNED_LONGLONGS_EQUAL(p.GetLastWriteTimestamp(), 12345ULL); -} - -TEST(PathTestGroup, Path_EqualityOperator) { - buildcc::internal::Path p = - buildcc::internal::Path::CreateExistingPath(current_file_path); - STRCMP_EQUAL(p.GetPathname().string().c_str(), - current_file_path.string().c_str()); - - buildcc::internal::Path newp = - buildcc::internal::Path::CreateNewPath(current_file_path, 12345ULL); - - // NOTE, Equality does not match the last_write_timestamp - // ONLY matches the string - CHECK(p == newp); - CHECK(p == current_file_path); - CHECK(p == current_file_path); -} - -TEST(PathTestGroup, Path_UnorderedSet) { - std::unordered_set - unique_paths; - - // Check inserts - CHECK_TRUE(unique_paths - .insert(buildcc::internal::Path::CreateExistingPath( - current_file_path)) - .second); - CHECK_FALSE(unique_paths - .insert(buildcc::internal::Path::CreateNewPath( - current_file_path, 12345ULL)) - .second); - CHECK_TRUE(unique_paths - .insert(buildcc::internal::Path::CreateNewPath( - "random_path_main.cpp", 98765ULL)) - .second); - - // Check finds - // * NOTE, Only matches pathname - CHECK_FALSE(unique_paths.find(buildcc::internal::Path::CreateExistingPath( - current_file_path)) == unique_paths.end()); - - CHECK_FALSE(unique_paths.find(buildcc::internal::Path::CreateNewPath( - current_file_path, 1111ULL)) == unique_paths.end()); - CHECK_FALSE(unique_paths.find(buildcc::internal::Path::CreateNewPath( - "random_path_main.cpp", 12345ULL)) == unique_paths.end()); - CHECK_TRUE(unique_paths.find(buildcc::internal::Path::CreateNewPath( - "incorrect_path_main.cpp", 0000ULL)) == unique_paths.end()); -} - -int main(int ac, char **av) { - return CommandLineTestRunner::RunAllTests(ac, av); -} diff --git a/buildcc/lib/target/test/target/CMakeLists.txt b/buildcc/lib/target/test/target/CMakeLists.txt index 066431b4..8e73ca20 100644 --- a/buildcc/lib/target/test/target/CMakeLists.txt +++ b/buildcc/lib/target/test/target/CMakeLists.txt @@ -1,13 +1,78 @@ configure_file(constants.h.in ${CMAKE_CURRENT_BINARY_DIR}/generated/constants.h @ONLY) -add_library(target_interface STATIC) -target_include_directories(target_interface PUBLIC +add_library(target_interface INTERFACE) +target_include_directories(target_interface INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/generated ) -target_link_libraries(target_interface PUBLIC +target_link_libraries(target_interface INTERFACE mock_target ) +# Toolchain + +add_executable(test_toolchain_flag_api + test_toolchain_flag_api.cpp +) +target_link_libraries(test_toolchain_flag_api PRIVATE target_interface) + +add_test(NAME test_toolchain_flag_api COMMAND test_toolchain_flag_api) + +# Interfaces +add_executable(test_serialization_interface + test_serialization_interface.cpp +) +target_link_libraries(test_serialization_interface PRIVATE target_interface) + +add_test(NAME test_serialization_interface COMMAND test_serialization_interface + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) + +# Common +add_executable(test_target_state + test_target_state.cpp +) +target_link_libraries(test_target_state PRIVATE target_interface) + +add_test(NAME test_target_state COMMAND test_target_state) + +# Generator +add_executable(test_custom_generator + test_custom_generator.cpp +) +target_link_libraries(test_custom_generator PRIVATE target_interface) + +add_executable(test_file_generator + test_file_generator.cpp +) +target_link_libraries(test_file_generator PRIVATE target_interface) + +add_executable(test_template_generator + test_template_generator.cpp +) +target_link_libraries(test_template_generator PRIVATE target_interface) + +add_test(NAME test_custom_generator COMMAND test_custom_generator + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) +add_test(NAME test_file_generator COMMAND test_file_generator + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) +add_test(NAME test_template_generator COMMAND test_template_generator + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) + +# Target friend + +add_executable(test_compile_object + test_compile_object.cpp +) +target_link_libraries(test_compile_object PRIVATE target_interface) + +add_test(NAME test_compile_object COMMAND test_compile_object + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) + +# Target # Test base target add_executable(test_base_target @@ -15,6 +80,12 @@ add_executable(test_base_target ) target_link_libraries(test_base_target PRIVATE target_interface) +# Test target pch +add_executable(test_target_pch + test_target_pch.cpp +) +target_link_libraries(test_target_pch PRIVATE target_interface) + # Test target sources add_executable(test_target_source test_target_source.cpp @@ -46,39 +117,41 @@ add_executable(test_target_external_lib ) target_link_libraries(test_target_external_lib PRIVATE target_interface) -# Test target preprocessor flags -add_executable(test_target_preprocessor_flags - test_target_preprocessor_flags.cpp -) -target_link_libraries(test_target_preprocessor_flags PRIVATE target_interface) # Test target c compile flags -add_executable(test_target_c_compile_flags - test_target_c_compile_flags.cpp +add_executable(test_target_flags + test_target_flags.cpp ) -target_link_libraries(test_target_c_compile_flags PRIVATE target_interface) +target_link_libraries(test_target_flags PRIVATE target_interface) -# Test target cpp compile flags -add_executable(test_target_cpp_compile_flags - test_target_cpp_compile_flags.cpp +# Test target user deps +add_executable(test_target_user_deps + test_target_user_deps.cpp ) -target_link_libraries(test_target_cpp_compile_flags PRIVATE target_interface) +target_link_libraries(test_target_user_deps PRIVATE target_interface) -# Test target link flags -add_executable(test_target_link_flags - test_target_link_flags.cpp +add_executable(test_target_sync + test_target_sync.cpp ) -target_link_libraries(test_target_link_flags PRIVATE target_interface) +target_link_libraries(test_target_sync PRIVATE target_interface) + +add_executable(test_target_failure_states + test_target_failure_states.cpp +) +target_link_libraries(test_target_failure_states PRIVATE target_interface) # Tests add_test(NAME test_base_target COMMAND test_base_target) +add_test(NAME test_target_pch COMMAND test_target_pch) add_test(NAME test_target_source COMMAND test_target_source) add_test(NAME test_target_source_out_of_root COMMAND test_target_source_out_of_root) add_test(NAME test_target_include_dir COMMAND test_target_include_dir) add_test(NAME test_target_lib_dep COMMAND test_target_lib_dep) add_test(NAME test_target_external_lib COMMAND test_target_external_lib) -add_test(NAME test_target_preprocessor_flags COMMAND test_target_preprocessor_flags) -add_test(NAME test_target_c_compile_flags COMMAND test_target_c_compile_flags) -add_test(NAME test_target_cpp_compile_flags COMMAND test_target_cpp_compile_flags) -add_test(NAME test_target_link_flags COMMAND test_target_link_flags) +add_test(NAME test_target_flags COMMAND test_target_flags) +add_test(NAME test_target_user_deps COMMAND test_target_user_deps) + +add_test(NAME test_target_sync COMMAND test_target_sync) + +add_test(NAME test_target_failure_states COMMAND test_target_failure_states) diff --git a/buildcc/lib/target/test/target/constants.h.in b/buildcc/lib/target/test/target/constants.h.in index 30f83d96..16c03b9d 100644 --- a/buildcc/lib/target/test/target/constants.h.in +++ b/buildcc/lib/target/test/target/constants.h.in @@ -4,6 +4,8 @@ inline constexpr char const *BUILD_SCRIPT_SOURCE = "@CMAKE_CURRENT_SOURCE_DIR@"; inline constexpr char const *BUILD_TARGET_BASE_INTERMEDIATE_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate"; + +inline constexpr char const *BUILD_TARGET_PCH_INTERMEDIATE_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate/target_pch"; inline constexpr char const * BUILD_TARGET_SOURCE_INTERMEDIATE_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate/target_source"; inline constexpr char const * BUILD_TARGET_SOURCE_OUT_OF_ROOT_INTERMEDIATE_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate/target_source_out_of_root"; @@ -11,7 +13,10 @@ inline constexpr char const * BUILD_TARGET_INCLUDE_DIR_INTERMEDIATE_DIR = "@CMAK inline constexpr char const * BUILD_TARGET_LIB_DEP_INTERMEDIATE_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate/target_lib_dep"; inline constexpr char const * BUILD_TARGET_EXTERNAL_LIB_INTERMEDIATE_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate/target_external_lib"; -inline constexpr char const * BUILD_TARGET_PREPROCESSOR_INTERMEDIATE_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate/target_preprocessor_flag"; -inline constexpr char const * BUILD_TARGET_C_COMPILE_INTERMEDIATE_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate/target_c_compile_flag"; -inline constexpr char const * BUILD_TARGET_CPP_COMPILE_INTERMEDIATE_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate/target_cpp_compile_flag"; -inline constexpr char const * BUILD_TARGET_LINK_INTERMEDIATE_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate/target_link_flag"; +inline constexpr char const * BUILD_TARGET_FLAG_INTERMEDIATE_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate/target_flag"; + +inline constexpr char const * BUILD_TARGET_USER_DEPS_INTERMEDIATE_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate/target_user_deps"; + +inline constexpr char const * BUILD_TARGET_SYNC_INTERMEDIATE_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate/target_sync"; + +inline constexpr char const * BUILD_TARGET_FAILURE_STATES_BUILD_DIR = "@CMAKE_CURRENT_SOURCE_DIR@/intermediate/target_failure_states"; diff --git a/buildcc/lib/target/test/target/data/asm/empty_asm.s b/buildcc/lib/target/test/target/data/asm/empty_asm.s new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/target/test/target/data/pch/pch_header_1.h b/buildcc/lib/target/test/target/data/pch/pch_header_1.h new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/target/test/target/data/pch/pch_header_2.h b/buildcc/lib/target/test/target/data/pch/pch_header_2.h new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/target/test/target/data/template/default_values.txt.in b/buildcc/lib/target/test/target/data/template/default_values.txt.in new file mode 100644 index 00000000..fa8699fd --- /dev/null +++ b/buildcc/lib/target/test/target/data/template/default_values.txt.in @@ -0,0 +1,4 @@ +{current_root_dir} +{current_build_dir} +{project_root_dir} +{project_build_dir} diff --git a/buildcc/lib/target/test/target/data/template/hello_world.txt.in b/buildcc/lib/target/test/target/data/template/hello_world.txt.in new file mode 100644 index 00000000..cd2ccb82 --- /dev/null +++ b/buildcc/lib/target/test/target/data/template/hello_world.txt.in @@ -0,0 +1 @@ +{hello} {world} diff --git a/buildcc/lib/target/test/target/test_base_target.cpp b/buildcc/lib/target/test/target/test_base_target.cpp index 01007393..a00c5a24 100644 --- a/buildcc/lib/target/test/target/test_base_target.cpp +++ b/buildcc/lib/target/test/target/test_base_target.cpp @@ -1,45 +1,101 @@ #include "constants.h" -#include "target.h" +#include "expect_command.h" -#include "env.h" +#include "target/target.h" + +#include "env/env.h" // NOTE, Make sure all these includes are AFTER the system and header includes #include "CppUTest/CommandLineTestRunner.h" #include "CppUTest/MemoryLeakDetectorNewMacros.h" #include "CppUTest/TestHarness.h" #include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" // clang-format off TEST_GROUP(TargetBaseTestGroup) { + void teardown() { + mock().clear(); + } }; // clang-format on -static const buildcc::base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", - "ld"); +static buildcc::Toolchain gcc(buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", + "ar", "ld")); TEST(TargetBaseTestGroup, InvalidTargetType) { constexpr const char *const INVALID_NAME = "Invalid.random"; - buildcc::env::init(BUILD_SCRIPT_SOURCE, BUILD_TARGET_BASE_INTERMEDIATE_DIR); + buildcc::Project::Init(BUILD_SCRIPT_SOURCE, + BUILD_TARGET_BASE_INTERMEDIATE_DIR); auto intermediate_path = fs::path(BUILD_TARGET_BASE_INTERMEDIATE_DIR) / gcc.GetName(); fs::remove_all(intermediate_path / INVALID_NAME); - CHECK_THROWS(std::exception, - buildcc::base::Target(INVALID_NAME, (buildcc::base::TargetType)3, - gcc, "")); + CHECK_THROWS( + std::exception, + buildcc::BaseTarget(INVALID_NAME, (buildcc::TargetType)3, gcc, "")); - buildcc::env::deinit(); + buildcc::Project::Deinit(); } TEST(TargetBaseTestGroup, NoEnvInit) { constexpr const char *const NAME = "Init.exe"; - CHECK_THROWS(std::exception, - buildcc::base::Target( - NAME, buildcc::base::TargetType::Executable, gcc, "data")); + CHECK_THROWS( + std::exception, + buildcc::BaseTarget(NAME, buildcc::TargetType::Executable, gcc, "data")); +} + +TEST(TargetBaseTestGroup, TargetConfig_BadCompileCommand) { + buildcc::Project::Init(BUILD_SCRIPT_SOURCE, + BUILD_TARGET_BASE_INTERMEDIATE_DIR); + fs::path target_source_intermediate_path = + buildcc::Project::GetBuildDir() / gcc.GetName(); + + constexpr const char *const NAME = "BadCompileCommand.exe"; + auto intermediate_path = target_source_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + { + buildcc::TargetConfig config; + config.compile_command = "{invalid_compile_string}"; + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data", config); + CHECK(simple.GetType() == buildcc::TargetType::Executable); + simple.AddSource("dummy_main.c"); + CHECK_THROWS(std::exception, simple.Build()); + } + + buildcc::Project::Deinit(); +} + +TEST(TargetBaseTestGroup, TargetConfig_BadLinkCommand) { + buildcc::Project::Init(BUILD_SCRIPT_SOURCE, + BUILD_TARGET_BASE_INTERMEDIATE_DIR); + fs::path target_source_intermediate_path = + buildcc::Project::GetBuildDir() / gcc.GetName(); + + constexpr const char *const NAME = "BadCompileCommand.exe"; + auto intermediate_path = target_source_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + { + buildcc::TargetConfig config; + config.link_command = "{invalid_link_string}"; + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data", config); + simple.AddSource("dummy_main.c"); + CHECK_THROWS(std::exception, simple.Build()); + } + + buildcc::Project::Deinit(); + mock().checkExpectations(); } // TODO, Check toolchain change diff --git a/buildcc/lib/target/test/target/test_compile_object.cpp b/buildcc/lib/target/test/target/test_compile_object.cpp new file mode 100644 index 00000000..d3cdcdff --- /dev/null +++ b/buildcc/lib/target/test/target/test_compile_object.cpp @@ -0,0 +1,39 @@ +#include "env/env.h" +#include "target/target.h" + +#include "target/friend/compile_object.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" + +// clang-format off +TEST_GROUP(TargetCompileObjectTestGroup) +{ +}; +// clang-format on + +static buildcc::Toolchain gcc(buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", + "ar", "ld")); + +TEST(TargetCompileObjectTestGroup, CacheCompileCommand_Invalid) { + buildcc::BaseTarget target("CacheCompileCommand_Invalid", + buildcc::TargetType::Executable, gcc, "data"); + buildcc::internal::CompileObject object(target); + + object.AddObjectData("random.invalid"); + + // Target not Built, which causes an exception + CHECK_THROWS(std::exception, object.CacheCompileCommands()); +} + +int main(int ac, char **av) { + buildcc::Project::Init(fs::current_path(), fs::current_path() / + "intermediate" / + "target_compile_object"); + fs::remove_all(buildcc::Project::GetBuildDir()); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/target/test/target/test_custom_generator.cpp b/buildcc/lib/target/test/target/test_custom_generator.cpp new file mode 100644 index 00000000..38c2c317 --- /dev/null +++ b/buildcc/lib/target/test/target/test_custom_generator.cpp @@ -0,0 +1,490 @@ +#include "target/custom_generator.h" + +#include "expect_command.h" +#include "expect_custom_generator.h" +#include "test_target_util.h" + +#include + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(CustomGeneratorTestGroup) +{ + void teardown() { + mock().checkExpectations(); + mock().clear(); + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + } +}; +// clang-format on + +const fs::path BUILD_DIR = + fs::current_path() / "intermediate" / "custom_generator"; + +static bool BasicGenerateCb(const buildcc::CustomGeneratorContext &ctx) { + (void)ctx; + return mock().actualCall("BasicGenerateCb").returnBoolValue(); +} + +TEST(CustomGeneratorTestGroup, Basic) { + buildcc::CustomGenerator cgen("basic", ""); + STRCMP_EQUAL(cgen.GetName().c_str(), "basic"); + cgen.AddIdInfo("id1", {"{current_root_dir}/dummy_main.c"}, + {"{current_build_dir}/dummy_main.o"}, BasicGenerateCb); + cgen.AddIdInfo("id2", {"{current_root_dir}/dummy_main.cpp"}, {}, + BasicGenerateCb); + cgen.Build(); + + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + buildcc::m::CustomGeneratorRunner(cgen); + + // Serialization check + { + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + + const auto &internal_map = serialization.GetLoad().internal_ids; + CHECK_EQUAL(internal_map.size(), 2); + const auto &id1_info = internal_map.at("id1"); + CHECK_EQUAL(id1_info.inputs.GetPathInfos().size(), 1); + CHECK_EQUAL(id1_info.outputs.GetPaths().size(), 1); + + const auto &id2_info = internal_map.at("id2"); + CHECK_EQUAL(id2_info.inputs.GetPathInfos().size(), 1); + CHECK_EQUAL(id2_info.outputs.GetPaths().size(), 0); + } +} + +TEST(CustomGeneratorTestGroup, BasicRebuild) { + constexpr const char *const kName = "basic_rebuild"; + + { + buildcc::CustomGenerator cgen(kName, ""); + cgen.AddIdInfo("id1", {"{current_root_dir}/dummy_main.c"}, + {"{current_build_dir}/dummy_main.o"}, BasicGenerateCb); + cgen.AddIdInfo("id2", {"{current_root_dir}/dummy_main.cpp"}, {}, + BasicGenerateCb); + cgen.Build(); + + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + buildcc::m::CustomGeneratorRunner(cgen); + } + + { + buildcc::CustomGenerator cgen(kName, ""); + cgen.AddIdInfo("id1", {"{current_root_dir}/dummy_main.c"}, + {"{current_build_dir}/dummy_main.o"}, BasicGenerateCb); + cgen.AddIdInfo("id2", {"{current_root_dir}/dummy_main.cpp"}, {}, + BasicGenerateCb); + cgen.Build(); + + buildcc::m::CustomGeneratorRunner(cgen); + } +} + +TEST(CustomGeneratorTestGroup, BasicRebuild_Add_Remove) { + constexpr const char *const kName = "basic_rebuild_add_remove"; + + { + buildcc::CustomGenerator cgen(kName, ""); + cgen.AddIdInfo("id1", {"{current_root_dir}/dummy_main.c"}, + {"{current_build_dir}/dummy_main.o"}, BasicGenerateCb); + cgen.AddIdInfo("id2", {"{current_root_dir}/dummy_main.cpp"}, {}, + BasicGenerateCb); + cgen.Build(); + + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + buildcc::m::CustomGeneratorRunner(cgen); + } + + // Remove + { + buildcc::CustomGenerator cgen(kName, ""); + cgen.AddIdInfo("id1", {"{current_root_dir}/dummy_main.c"}, + {"{current_build_dir}/dummy_main.o"}, BasicGenerateCb); + // ID2 Removed + cgen.Build(); + + buildcc::m::CustomGeneratorExpect_IdRemoved(1, &cgen); + buildcc::m::CustomGeneratorRunner(cgen); + } + + // Add + { + buildcc::CustomGenerator cgen(kName, ""); + cgen.AddIdInfo("id1", {"{current_root_dir}/dummy_main.c"}, + {"{current_build_dir}/dummy_main.o"}, BasicGenerateCb); + cgen.AddIdInfo("id2", {"{current_root_dir}/dummy_main.cpp"}, {}, + BasicGenerateCb); + cgen.Build(); + + buildcc::m::CustomGeneratorExpect_IdAdded(1, &cgen); + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + buildcc::m::CustomGeneratorRunner(cgen); + } +} + +TEST(CustomGeneratorTestGroup, Basic_Failure) { + buildcc::CustomGenerator cgen("basic_failure", ""); + cgen.AddIdInfo("id1", {"{current_root_dir}/dummy_main.c"}, {}, + BasicGenerateCb); + cgen.AddIdInfo("id2", {"{current_root_dir}/dummy_main.cpp"}, {}, + BasicGenerateCb); + cgen.Build(); + + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + mock().expectOneCall("BasicGenerateCb").andReturnValue(false); + buildcc::m::CustomGeneratorRunner(cgen); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + + // Load + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + + const auto &internal_map = serialization.GetLoad().internal_ids; + CHECK_EQUAL(internal_map.size(), 1); +} + +TEST(CustomGeneratorTestGroup, DefaultArgumentUsage) { + buildcc::CustomGenerator cgen("default_argument_usage", ""); + cgen.AddPatterns({{"dummy_main_c", "{current_root_dir}/dummy_main.c"}, + {"dummy_main_o", "{current_build_dir}/dummy_main.o"}, + {"dummy_main_cpp", "{current_root_dir}/dummy_main.cpp"}, + {"hello", "world"}}); + STRCMP_EQUAL(cgen.ParsePattern("{hello}").c_str(), "world"); + STRCMP_EQUAL(cgen.Get("hello").c_str(), "world"); + + cgen.AddIdInfo("id1", {"{dummy_main_c}"}, {"{dummy_main_o}"}, + BasicGenerateCb); + cgen.AddIdInfo("id2", {"{dummy_main_cpp}"}, {}, BasicGenerateCb); + cgen.Build(); + + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + buildcc::m::CustomGeneratorRunner(cgen); + + // Serialization check + { + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + + const auto &internal_map = serialization.GetLoad().internal_ids; + CHECK_EQUAL(internal_map.size(), 2); + const auto &id1_info = internal_map.at("id1"); + CHECK_EQUAL(id1_info.inputs.GetPathInfos().size(), 1); + CHECK_EQUAL(id1_info.outputs.GetPaths().size(), 1); + + const auto &id2_info = internal_map.at("id2"); + CHECK_EQUAL(id2_info.inputs.GetPathInfos().size(), 1); + CHECK_EQUAL(id2_info.outputs.GetPaths().size(), 0); + } +} + +TEST(CustomGeneratorTestGroup, FailureCases) { + { + buildcc::CustomGenerator cgen("failure_no_cb", ""); + buildcc::GenerateCb cb; + CHECK_THROWS(std::exception, cgen.AddIdInfo("id1", {}, {}, cb)); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + { + buildcc::CustomGenerator cgen("failure_cannot_save", ""); + fs::create_directory( + cgen.GetBinaryPath()); // make a folder so that file cannot be saved + + cgen.AddIdInfo("id1", {}, {}, BasicGenerateCb); + cgen.AddIdInfo("id2", {}, {}, BasicGenerateCb); + cgen.Build(); + + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + buildcc::m::CustomGeneratorRunner(cgen); + + CHECK_TRUE(buildcc::env::get_task_state() == + buildcc::env::TaskState::FAILURE); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + { + buildcc::CustomGenerator cgen("gen_task_not_run_no_io", ""); + cgen.Build(); + + buildcc::m::CustomGeneratorRunner(cgen); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + { + buildcc::env::set_task_state(buildcc::env::TaskState::FAILURE); + + buildcc::CustomGenerator cgen("gen_task_state_failure", ""); + cgen.AddIdInfo("id1", {}, {}, BasicGenerateCb); + cgen.Build(); + buildcc::m::CustomGeneratorRunner(cgen); + + CHECK_TRUE(buildcc::env::get_task_state() == + buildcc::env::TaskState::FAILURE); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); +} + +static bool RealGenerateCb(const buildcc::CustomGeneratorContext &ctx) { + (void)ctx; + mock().actualCall("RealGenerateCb"); + return buildcc::env::Command::Execute(""); +} + +TEST(CustomGeneratorTestGroup, RealGenerate_Basic) { + constexpr const char *const kGenName = "real_generator_basic"; + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddIdInfo("id1", {"{current_root_dir}/dummy_main.cpp"}, + {"{current_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddIdInfo("id2", {"{current_root_dir}/dummy_main.c"}, + {"{current_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); + + fs::remove_all(cgen.GetBinaryPath()); + } + + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddIdInfo("id1", {"{current_root_dir}/dummy_main.cpp"}, {}, + RealGenerateCb); + cgen.AddIdInfo("id2", {"{current_root_dir}/dummy_main.c"}, {}, + RealGenerateCb); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, false); + + // Since there is an error above, second command does not execute (note, + // this is the behaviour in a single thread that is why we can + // check sequentially) + buildcc::m::CustomGeneratorRunner(cgen); + + CHECK_TRUE(buildcc::env::get_task_state() == + buildcc::env::TaskState::FAILURE); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 0); + + fs::remove_all(cgen.GetBinaryPath()); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); +} + +TEST(CustomGeneratorTestGroup, RealGenerate_Update_Success) { + constexpr const char *const kGenName = "real_generator_update_success"; + + { + buildcc::CustomGenerator cgen(kGenName, ""); + buildcc::env::save_file( + (cgen.GetBuildDir() / "dummy_main.c").string().c_str(), "", false); + buildcc::env::save_file( + (cgen.GetBuildDir() / "dummy_main.cpp").string().c_str(), "", false); + + cgen.AddIdInfo("id1", {"{current_build_dir}/dummy_main.c"}, + {"{current_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddIdInfo("id2", {"{current_build_dir}/dummy_main.cpp"}, + {"{current_build_dir}/other_dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); + auto imap = serialization.GetLoad().internal_ids; + CHECK_EQUAL(imap.at("id1").inputs.GetPathInfos().size(), 1); + CHECK_EQUAL(imap.at("id2").inputs.GetPathInfos().size(), 1); + + CHECK_EQUAL(imap.at("id1").outputs.GetPaths().size(), 1); + CHECK_EQUAL(imap.at("id2").outputs.GetPaths().size(), 1); + } + + buildcc::m::blocking_sleep(1); + + // Updated Input file Success + UT_PRINT("Updated Input file: Success\r\n"); + { + buildcc::CustomGenerator cgen(kGenName, ""); + buildcc::env::save_file( + (cgen.GetBuildDir() / "dummy_main.cpp").string().c_str(), "", false); + + auto last_write_timestamp = static_cast( + fs::last_write_time(cgen.GetBuildDir() / "dummy_main.cpp") + .time_since_epoch() + .count()); + + cgen.AddIdInfo("id1", {"{current_build_dir}/dummy_main.c"}, + {"{current_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddIdInfo("id2", {"{current_build_dir}/dummy_main.cpp"}, + {"{current_build_dir}/other_dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); + auto imap = serialization.GetLoad().internal_ids; + CHECK_EQUAL(imap.at("id1").inputs.GetPathInfos().size(), 1); + CHECK_EQUAL(imap.at("id1").outputs.GetPaths().size(), 1); + + CHECK_EQUAL(imap.at("id2").inputs.GetPathInfos().size(), 1); + CHECK_EQUAL(imap.at("id2").outputs.GetPaths().size(), 1); + + STRCMP_EQUAL(std::to_string(last_write_timestamp).c_str(), + imap.at("id2").inputs.GetPathInfos()[0].hash.c_str()); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + } + + // Updated Output file Success + UT_PRINT("Updated Output file: Success\r\n"); + { + buildcc::CustomGenerator cgen(kGenName, ""); + + cgen.AddIdInfo("id1", {"{current_build_dir}/dummy_main.c"}, + {"{current_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddIdInfo("id2", {"{current_build_dir}/dummy_main.cpp"}, + {"{current_build_dir}/rename_dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); + auto imap = serialization.GetLoad().internal_ids; + CHECK_EQUAL(imap.at("id1").inputs.GetPathInfos().size(), 1); + CHECK_EQUAL(imap.at("id1").outputs.GetPaths().size(), 1); + + CHECK_EQUAL(imap.at("id2").inputs.GetPathInfos().size(), 1); + CHECK_EQUAL(imap.at("id2").outputs.GetPaths().size(), 1); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + } +} + +class MyCustomBlobHandler : public buildcc::CustomBlobHandler { +public: + MyCustomBlobHandler(int32_t my_recheck_value) + : recheck_value(my_recheck_value) {} + +private: + int32_t recheck_value = 0; + +private: + bool Verify(const std::vector &serialized_data) const override { + json j = json::from_msgpack(serialized_data); + return !j.is_discarded(); + } + + bool IsEqual(const std::vector &previous, + const std::vector ¤t) const override { + return Deserialize(previous) == Deserialize(current); + } + + std::vector Serialize() const override { + json j = recheck_value; + return json::to_msgpack(j); + } + + // serialized_data has already been verified + int32_t Deserialize(const std::vector &serialized_data) const { + json j = json::from_msgpack(serialized_data); + int32_t deserialized; + j.get_to(deserialized); + return deserialized; + } +}; + +TEST(CustomGeneratorTestGroup, RealGenerate_BasicBlobRecheck) { + constexpr const char *const kGenName = "real_generator_basic_blob_recheck"; + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddIdInfo("id1", {"{current_root_dir}/dummy_main.cpp"}, + {"{current_build_dir}/dummy_main.o"}, RealGenerateCb, + std::make_shared(12)); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 1); + } + + // Rebuild + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddIdInfo("id1", {"{current_root_dir}/dummy_main.cpp"}, + {"{current_build_dir}/dummy_main.o"}, RealGenerateCb, + std::make_shared(200)); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 1); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); +} + +int main(int ac, char **av) { + fs::remove_all(BUILD_DIR); + buildcc::Project::Init(fs::current_path() / "data", BUILD_DIR); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/target/test/target/test_file_generator.cpp b/buildcc/lib/target/test/target/test_file_generator.cpp new file mode 100644 index 00000000..278aa7a6 --- /dev/null +++ b/buildcc/lib/target/test/target/test_file_generator.cpp @@ -0,0 +1,467 @@ +#include "target/file_generator.h" + +#include "expect_command.h" +#include "expect_custom_generator.h" +#include "test_target_util.h" + +#include "taskflow/taskflow.hpp" + +#include "env/util.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(FileGeneratorTestGroup) +{ + void teardown() { + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + mock().clear(); + } +}; +// clang-format on + +fs::path BUILD_DIR = fs::current_path() / "intermediate" / "file_generator"; + +TEST(FileGeneratorTestGroup, Generator_Build) { + constexpr const char *const NAME = "Build"; + buildcc::FileGenerator generator(NAME, ""); + + generator.AddPatterns({ + {"compiler", "gcc"}, + }); + + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c"); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + + mock().checkExpectations(); +} + +TEST(FileGeneratorTestGroup, Generator_Identifier) { + constexpr const char *const NAME = "Identifier"; + buildcc::FileGenerator generator(NAME, ""); + + generator.AddPatterns({ + {"compiler", "gcc"}, + }); + generator.AddPattern("dummy_main_c", "{current_root_dir}/dummy_main.c"); + generator.AddPattern("dummy_main_exe", "{current_build_dir}/dummy_main.exe"); + + generator.AddInput("{dummy_main_c}"); + generator.AddOutput("{dummy_main_exe}"); + generator.AddCommand("{compiler} -o {dummy_main_exe} {dummy_main_c}"); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + + mock().checkExpectations(); +} + +TEST(FileGeneratorTestGroup, Generator_Rebuild) { + constexpr const char *const NAME = "Rebuild"; + { + buildcc::FileGenerator generator(NAME, ""); + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c", + { + {"compiler", "gcc"}, + }); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + } + + { + buildcc::FileGenerator generator(NAME, ""); + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c", + { + {"compiler", "gcc"}, + }); + + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + } + + mock().checkExpectations(); +} + +TEST(FileGeneratorTestGroup, Generator_Rebuild_Inputs) { + constexpr const char *const NAME = "Rebuild_Inputs"; + + { + buildcc::FileGenerator generator(NAME, ""); + generator.AddInput("{current_root_dir}/new_source.cpp"); + generator.AddOutput("{current_build_dir}/new_source.exe"); + generator.AddCommand("gcc -o {current_build_dir}/new_source.exe " + "{current_root_dir}/new_source.cpp"); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + } + + // Removed + { + buildcc::FileGenerator generator(NAME, ""); + generator.AddOutput("{current_build_dir}/new_source.exe"); + generator.AddCommand("gcc -o {current_build_dir}/new_source.exe " + "{current_root_dir}/new_source.cpp"); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + } + + // Added + { + buildcc::FileGenerator generator(NAME, ""); + generator.AddInput("{current_root_dir}/new_source.cpp"); + generator.AddOutput("{current_build_dir}/new_source.cpp.exe"); + generator.AddCommand("gcc -o {current_build_dir}/new_source.cpp.exe " + "{current_root_dir}/new_source.cpp"); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + } + + buildcc::m::blocking_sleep(1); + bool saved = buildcc::env::save_file( + (buildcc::Project::GetRootDir() / "new_source.cpp").string().c_str(), "", + false); + CHECK_TRUE(saved); + + // Updated + { + buildcc::FileGenerator generator(NAME, ""); + generator.AddInput("{current_root_dir}/new_source.cpp"); + generator.AddOutput("{current_build_dir}/new_source.cpp.exe"); + generator.AddCommand("gcc -o {current_build_dir}/new_source.cpp.exe " + "{current_root_dir}/new_source.cpp"); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + } + + mock().checkExpectations(); +} + +TEST(FileGeneratorTestGroup, Generator_Rebuild_Outputs) { + constexpr const char *const NAME = "Rebuild_Outputs"; + { + buildcc::FileGenerator generator(NAME, ""); + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c", + { + {"compiler", "gcc"}, + }); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + } + + { + buildcc::FileGenerator generator(NAME, ""); + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c", + { + {"compiler", "gcc"}, + }); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + } + + { + buildcc::FileGenerator generator(NAME, ""); + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c", + { + {"compiler", "gcc"}, + }); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + } + + mock().checkExpectations(); +} + +TEST(FileGeneratorTestGroup, Generator_Rebuild_Commands) { + constexpr const char *const NAME = "Rebuild_Commands"; + { + buildcc::FileGenerator generator(NAME, ""); + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c", + { + {"compiler", "gcc"}, + }); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + } + + { + buildcc::FileGenerator generator(NAME, ""); + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} {current_root_dir}/dummy_main.c", + { + {"compiler", "gcc"}, + }); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + } + + { + buildcc::FileGenerator generator(NAME, ""); + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("gcc -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c"); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + } + + mock().checkExpectations(); +} + +TEST(FileGeneratorTestGroup, Generator_AddDefaultArguments) { + constexpr const char *const NAME = "AddDefaultArgument"; + buildcc::FileGenerator generator(NAME, ""); + + generator.AddPatterns({ + {"key", "value"}, + }); + const std::string &value = generator.Get("key"); + STRCMP_EQUAL(value.c_str(), "value"); + STRCMP_EQUAL(generator.GetName().c_str(), "AddDefaultArgument"); +} + +// FAILURE STATES + +TEST(FileGeneratorTestGroup, Generator_FailedEnvTaskState) { + buildcc::env::set_task_state(buildcc::env::TaskState::FAILURE); + + constexpr const char *const NAME = "FailedEnvTaskState"; + buildcc::FileGenerator generator(NAME, ""); + + generator.AddPatterns({ + {"compiler", "gcc"}, + }); + + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c"); + + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + + mock().checkExpectations(); + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); +} + +TEST(FileGeneratorTestGroup, Generator_FailedGenerateConvert) { + constexpr const char *const NAME = "FailedGenerateConvert"; + buildcc::FileGenerator generator(NAME, ""); + + generator.AddPatterns({ + {"compiler", "gcc"}, + }); + + generator.AddInput("{current_root_dir}/this_file_does_not_exist.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c"); + + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + + mock().checkExpectations(); + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); +} + +TEST(FileGeneratorTestGroup, Generator_FailedGenerateCommand) { + constexpr const char *const NAME = "FailedGenerateCommand"; + buildcc::FileGenerator generator(NAME, ""); + + generator.AddPatterns({ + {"compiler", "gcc"}, + }); + + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c"); + + buildcc::env::m::CommandExpect_Execute(1, false); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + + mock().checkExpectations(); + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); +} + +TEST(FileGeneratorTestGroup, Generator_FailedStore) { + constexpr const char *const NAME = "FailedStore"; + const fs::path test_build_dir = buildcc::Project::GetBuildDir() / NAME; + + buildcc::FileGenerator generator(NAME, ""); + fs::remove_all(test_build_dir); + + generator.AddPatterns({ + {"compiler", "gcc"}, + }); + + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c"); + + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + + // CHECK(generator.GetTaskState() == buildcc::env::TaskState::FAILURE); + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + + mock().checkExpectations(); + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); +} + +TEST(FileGeneratorTestGroup, FailedEnvTaskState_Rebuild) { + buildcc::env::set_task_state(buildcc::env::TaskState::FAILURE); + + constexpr const char *const NAME = "FailedEnvTaskState_Rebuild"; + { + buildcc::FileGenerator generator(NAME, ""); + + generator.AddPatterns({ + {"compiler", "gcc"}, + }); + + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c"); + + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + } + + // reset + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + // rebuild + { + buildcc::FileGenerator generator(NAME, ""); + + generator.AddPatterns({ + {"compiler", "gcc"}, + }); + + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c"); + + generator.Build(); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(generator); + } + + mock().checkExpectations(); +} + +TEST(FileGeneratorTestGroup, FailedGenerateCommand_Rebuild) { + constexpr const char *const NAME = "FailedGenerateCommand_Rebuild"; + + { + buildcc::FileGenerator generator(NAME, ""); + + generator.AddPatterns({ + {"compiler", "gcc"}, + }); + + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c"); + + buildcc::env::m::CommandExpect_Execute(1, false); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + } + + // reset + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + // rebuild + { + buildcc::FileGenerator generator(NAME, ""); + + generator.AddPatterns({ + {"compiler", "gcc"}, + }); + + generator.AddInput("{current_root_dir}/dummy_main.c"); + generator.AddOutput("{current_build_dir}/dummy_main.exe"); + generator.AddCommand("{compiler} -o {current_build_dir}/dummy_main.exe " + "{current_root_dir}/dummy_main.c"); + + buildcc::m::CustomGeneratorExpect_IdAdded(1, &generator); + buildcc::env::m::CommandExpect_Execute(1, true); + generator.Build(); + buildcc::m::CustomGeneratorRunner(generator); + } + + mock().checkExpectations(); +} + +int main(int ac, char **av) { + fs::remove_all(BUILD_DIR); + buildcc::Project::Init(fs::current_path() / "data", BUILD_DIR); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/target/test/target/test_serialization_interface.cpp b/buildcc/lib/target/test/target/test_serialization_interface.cpp new file mode 100644 index 00000000..e95db8d1 --- /dev/null +++ b/buildcc/lib/target/test/target/test_serialization_interface.cpp @@ -0,0 +1,115 @@ +#include "schema/interface/serialization_interface.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +class TestSerializationInterface + : public buildcc::internal::SerializationInterface { +public: + TestSerializationInterface(const fs::path &serialized_file) + : SerializationInterface(serialized_file) {} + + void VerifyExpectation(int calls, bool return_value) { + mock() + .expectNCalls(calls, "verify") + .onObject(this) + .andReturnValue(return_value); + } + + void LoadExpectation(int calls, bool return_value) { + mock() + .expectNCalls(calls, "load") + .onObject(this) + .andReturnValue(return_value); + } + + void StoreExpectation(int calls, bool return_value) { + mock() + .expectNCalls(calls, "store") + .onObject(this) + .andReturnValue(return_value); + } + +private: + bool Verify(const std::string &serialized_data) override { + (void)serialized_data; + return mock().actualCall("verify").onObject(this).returnBoolValue(); + } + + bool Load(const std::string &serialized_data) override { + (void)serialized_data; + return mock().actualCall("load").onObject(this).returnBoolValue(); + } + + bool Store(const fs::path &absolute_serialized_file) override { + (void)absolute_serialized_file; + return mock().actualCall("store").onObject(this).returnBoolValue(); + } +}; + +// clang-format off +TEST_GROUP(TestSerializationInterfaceGroup) +{ + void teardown() { + mock().clear(); + } +}; +// clang-format on + +TEST(TestSerializationInterfaceGroup, Verify_False) { + TestSerializationInterface test_serialization_interface( + fs::current_path() / "data" / "dummy_main.c"); + + test_serialization_interface.VerifyExpectation(1, false); + bool loaded = test_serialization_interface.LoadFromFile(); + CHECK_FALSE(loaded); +} + +TEST(TestSerializationInterfaceGroup, Load_False) { + TestSerializationInterface test_serialization_interface( + fs::current_path() / "data" / "dummy_main.c"); + + test_serialization_interface.VerifyExpectation(1, true); + test_serialization_interface.LoadExpectation(1, false); + bool loaded = test_serialization_interface.LoadFromFile(); + CHECK_FALSE(loaded); +} + +TEST(TestSerializationInterfaceGroup, Load_True) { + TestSerializationInterface test_serialization_interface( + fs::current_path() / "data" / "dummy_main.c"); + + test_serialization_interface.VerifyExpectation(1, true); + test_serialization_interface.LoadExpectation(1, true); + bool loaded = test_serialization_interface.LoadFromFile(); + CHECK_TRUE(loaded); +} + +TEST(TestSerializationInterfaceGroup, Store_False) { + TestSerializationInterface test_serialization_interface( + fs::current_path() / "data" / "dummy_main.c"); + test_serialization_interface.StoreExpectation(1, false); + bool stored = test_serialization_interface.StoreToFile(); + CHECK_FALSE(stored); +} + +TEST(TestSerializationInterfaceGroup, Store_True) { + TestSerializationInterface test_serialization_interface( + fs::current_path() / "data" / "dummy_main.c"); + test_serialization_interface.StoreExpectation(1, true); + bool stored = test_serialization_interface.StoreToFile(); + CHECK_TRUE(stored); + + std::string serialized_file = + test_serialization_interface.GetSerializedFile().string(); + std::string compare = (fs::current_path() / "data" / "dummy_main.c").string(); + STRCMP_EQUAL(serialized_file.c_str(), compare.c_str()); +} + +int main(int ac, char **av) { + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/target/test/target/test_target_c_compile_flags.cpp b/buildcc/lib/target/test/target/test_target_c_compile_flags.cpp deleted file mode 100644 index 2e9bb227..00000000 --- a/buildcc/lib/target/test/target/test_target_c_compile_flags.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#include "constants.h" - -#include "expect_target.h" -#include "target.h" - -#include "env.h" - -// -#include "internal/fbs_loader.h" - -// Third Party -#include "flatbuffers/util.h" - -// NOTE, Make sure all these includes are AFTER the system and header includes -#include "CppUTest/CommandLineTestRunner.h" -#include "CppUTest/MemoryLeakDetectorNewMacros.h" -#include "CppUTest/TestHarness.h" -#include "CppUTest/Utest.h" -#include "CppUTestExt/MockSupport.h" - -// clang-format off -TEST_GROUP(TargetTestCCompileFlagsGroup) -{ - void teardown() { - mock().clear(); - } -}; -// clang-format on - -static const buildcc::base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", - "ld"); -static const fs::path target_source_intermediate_path = - fs::path(BUILD_TARGET_C_COMPILE_INTERMEDIATE_DIR) / gcc.GetName(); - -TEST(TargetTestCCompileFlagsGroup, Target_AddCompileFlag) { - constexpr const char *const NAME = "AddCCompileFlag.exe"; - constexpr const char *const DUMMY_MAIN = "dummy_main.c"; - - auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; - auto intermediate_path = target_source_intermediate_path / NAME; - - // Delete - fs::remove_all(intermediate_path); - - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, gcc, - "data"); - simple.AddSource(DUMMY_MAIN); - simple.AddCCompileFlag("-std=c11"); - - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - - mock().checkExpectations(); - - // Verify binary - buildcc::internal::FbsLoader loader(NAME, simple.GetTargetIntermediateDir()); - bool loaded = loader.Load(); - CHECK_TRUE(loaded); - - CHECK_EQUAL(loader.GetLoadedCCompileFlags().size(), 1); -} - -TEST(TargetTestCCompileFlagsGroup, Target_ChangedCompileFlag) { - constexpr const char *const NAME = "ChangedCCompileFlag.exe"; - constexpr const char *const DUMMY_MAIN = "dummy_main.c"; - - auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; - auto intermediate_path = target_source_intermediate_path / NAME; - - // Delete - fs::remove_all(intermediate_path); - - { - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - simple.AddSource(DUMMY_MAIN); - simple.AddCCompileFlag("-std=c11"); - - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - } - { - // * Remove flag - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - simple.AddSource(DUMMY_MAIN); - buildcc::base::m::TargetExpect_FlagChanged(1, &simple); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - } - - { - // * Add flag - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - simple.AddSource(DUMMY_MAIN); - simple.AddCCompileFlag("-std=c11"); - buildcc::base::m::TargetExpect_FlagChanged(1, &simple); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - } - - mock().checkExpectations(); -} - -int main(int ac, char **av) { - buildcc::env::init(BUILD_SCRIPT_SOURCE, - BUILD_TARGET_C_COMPILE_INTERMEDIATE_DIR); - return CommandLineTestRunner::RunAllTests(ac, av); -} diff --git a/buildcc/lib/target/test/target/test_target_cpp_compile_flags.cpp b/buildcc/lib/target/test/target/test_target_cpp_compile_flags.cpp deleted file mode 100644 index e2565149..00000000 --- a/buildcc/lib/target/test/target/test_target_cpp_compile_flags.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#include "constants.h" - -#include "expect_target.h" -#include "target.h" - -#include "env.h" - -// -#include "internal/fbs_loader.h" - -// Third Party -#include "flatbuffers/util.h" - -// NOTE, Make sure all these includes are AFTER the system and header includes -#include "CppUTest/CommandLineTestRunner.h" -#include "CppUTest/MemoryLeakDetectorNewMacros.h" -#include "CppUTest/TestHarness.h" -#include "CppUTest/Utest.h" -#include "CppUTestExt/MockSupport.h" - -// clang-format off -TEST_GROUP(TargetTestCppCompileFlagsGroup) -{ - void teardown() { - mock().clear(); - } -}; -// clang-format on - -static const buildcc::base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", - "ld"); -static const fs::path target_source_intermediate_path = - fs::path(BUILD_TARGET_CPP_COMPILE_INTERMEDIATE_DIR) / gcc.GetName(); - -TEST(TargetTestCppCompileFlagsGroup, Target_AddCompileFlag) { - constexpr const char *const NAME = "AddCppCompileFlag.exe"; - constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; - - auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; - auto intermediate_path = target_source_intermediate_path / NAME; - - // Delete - fs::remove_all(intermediate_path); - - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, gcc, - "data"); - simple.AddSource(DUMMY_MAIN); - simple.AddCppCompileFlag("-std=c++17"); - - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - - mock().checkExpectations(); - - // Verify binary - buildcc::internal::FbsLoader loader(NAME, simple.GetTargetIntermediateDir()); - bool loaded = loader.Load(); - CHECK_TRUE(loaded); - - CHECK_EQUAL(loader.GetLoadedCppCompileFlags().size(), 1); -} - -TEST(TargetTestCppCompileFlagsGroup, Target_ChangedCompileFlag) { - constexpr const char *const NAME = "ChangedCppCompileFlag.exe"; - constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; - - auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; - auto intermediate_path = target_source_intermediate_path / NAME; - - // Delete - fs::remove_all(intermediate_path); - - { - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - simple.AddSource(DUMMY_MAIN); - simple.AddCCompileFlag("-std=c++17"); - - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - } - { - // * Remove flag - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - simple.AddSource(DUMMY_MAIN); - buildcc::base::m::TargetExpect_FlagChanged(1, &simple); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - } - - { - // * Add flag - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - simple.AddSource(DUMMY_MAIN); - simple.AddCCompileFlag("-std=c++17"); - buildcc::base::m::TargetExpect_FlagChanged(1, &simple); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - } - - mock().checkExpectations(); -} - -int main(int ac, char **av) { - buildcc::env::init(BUILD_SCRIPT_SOURCE, - BUILD_TARGET_CPP_COMPILE_INTERMEDIATE_DIR); - return CommandLineTestRunner::RunAllTests(ac, av); -} diff --git a/buildcc/lib/target/test/target/test_target_external_lib.cpp b/buildcc/lib/target/test/target/test_target_external_lib.cpp index 3fa49bd4..e6f76cef 100644 --- a/buildcc/lib/target/test/target/test_target_external_lib.cpp +++ b/buildcc/lib/target/test/target/test_target_external_lib.cpp @@ -1,16 +1,15 @@ #include "constants.h" -#include "logging.h" +#include "env/logging.h" +#include "expect_command.h" #include "expect_target.h" -#include "target.h" -#include "internal/fbs_loader.h" +#include "target/target.h" -#include "flatbuffers/util.h" +#include "schema/target_serialization.h" #include -#include // NOTE, Make sure all these includes are AFTER the system and header includes #include "CppUTest/CommandLineTestRunner.h" @@ -28,8 +27,10 @@ TEST_GROUP(TargetTestExternalLib) }; // clang-format on -static const buildcc::base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", - "ld"); +static buildcc::Toolchain gcc(buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", + "ar", "ld")); + static const fs::path intermediate_path = fs::path(BUILD_TARGET_EXTERNAL_LIB_INTERMEDIATE_DIR) / gcc.GetName(); @@ -38,24 +39,26 @@ TEST(TargetTestExternalLib, TestAddLibDir) { fs::remove_all(intermediate_path / EXENAME); - buildcc::base::Target exe(EXENAME, buildcc::base::TargetType::StaticLibrary, - gcc, "data"); + buildcc::BaseTarget exe(EXENAME, buildcc::TargetType::StaticLibrary, gcc, + "data"); exe.AddSource("foo_main.cpp"); exe.AddLibDir(exe.GetTargetPath().parent_path()); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); exe.Build(); + buildcc::m::TargetRunner(exe); + CHECK_TRUE(exe.IsBuilt()); mock().checkExpectations(); // Verify binary - buildcc::internal::FbsLoader loader(EXENAME, exe.GetTargetIntermediateDir()); - bool loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization( + exe.GetTargetBuildDir() / (std::string(EXENAME) + ".bin")); + bool loaded = serialization.LoadFromFile(); CHECK_TRUE(loaded); - - CHECK_EQUAL(loader.GetLoadedLibDirs().size(), 1); - CHECK_EQUAL(loader.GetLoadedExternalLibDeps().size(), 0); + CHECK_EQUAL(serialization.GetLoad().lib_dirs.GetPaths().size(), 1); + CHECK_EQUAL(serialization.GetLoad().external_libs.size(), 0); } TEST(TargetTestExternalLib, TestAddExternalLibDep_Simple) { @@ -63,25 +66,28 @@ TEST(TargetTestExternalLib, TestAddExternalLibDep_Simple) { fs::remove_all(intermediate_path / EXENAME); - buildcc::base::Target exe(EXENAME, buildcc::base::TargetType::StaticLibrary, - gcc, "data"); + buildcc::BaseTarget exe(EXENAME, buildcc::TargetType::StaticLibrary, gcc, + "data"); exe.AddSource("foo_main.cpp"); exe.AddLibDir(exe.GetTargetPath().parent_path()); exe.AddLibDep("-lfoo"); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); exe.Build(); + buildcc::m::TargetRunner(exe); + CHECK_TRUE(exe.IsBuilt()); mock().checkExpectations(); // Verify binary - buildcc::internal::FbsLoader loader(EXENAME, exe.GetTargetIntermediateDir()); - bool loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization( + exe.GetTargetBuildDir() / (std::string(EXENAME) + ".bin")); + bool loaded = serialization.LoadFromFile(); CHECK_TRUE(loaded); - CHECK_EQUAL(loader.GetLoadedLibDirs().size(), 1); - CHECK_EQUAL(loader.GetLoadedExternalLibDeps().size(), 1); + CHECK_EQUAL(serialization.GetLoad().lib_dirs.GetPaths().size(), 1); + CHECK_EQUAL(serialization.GetLoad().external_libs.size(), 1); } TEST(TargetTestExternalLib, TestAddExternalLibDep_RebuildChanged) { @@ -91,46 +97,52 @@ TEST(TargetTestExternalLib, TestAddExternalLibDep_RebuildChanged) { // First build { - buildcc::base::Target exe(EXENAME, buildcc::base::TargetType::StaticLibrary, - gcc, "data"); + buildcc::BaseTarget exe(EXENAME, buildcc::TargetType::StaticLibrary, gcc, + "data"); exe.AddSource("foo_main.cpp"); exe.AddLibDir(exe.GetTargetPath().parent_path()); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); exe.Build(); + buildcc::m::TargetRunner(exe); + CHECK_TRUE(exe.IsBuilt()); } // Add { - buildcc::base::Target exe(EXENAME, buildcc::base::TargetType::StaticLibrary, - gcc, "data"); + buildcc::BaseTarget exe(EXENAME, buildcc::TargetType::StaticLibrary, gcc, + "data"); exe.AddSource("foo_main.cpp"); exe.AddLibDir(exe.GetTargetPath().parent_path()); exe.AddLibDep("-lfoo"); - buildcc::base::m::TargetExpect_ExternalLibChanged(1, &exe); - buildcc::internal::m::Expect_command(1, true); + buildcc::m::TargetExpect_ExternalLibChanged(1, &exe); + buildcc::env::m::CommandExpect_Execute(1, true); exe.Build(); + buildcc::m::TargetRunner(exe); + CHECK_TRUE(exe.IsBuilt()); } // Remove { - buildcc::base::Target exe(EXENAME, buildcc::base::TargetType::StaticLibrary, - gcc, "data"); + buildcc::BaseTarget exe(EXENAME, buildcc::TargetType::StaticLibrary, gcc, + "data"); exe.AddSource("foo_main.cpp"); exe.AddLibDir(exe.GetTargetPath().parent_path()); - buildcc::base::m::TargetExpect_ExternalLibChanged(1, &exe); - buildcc::internal::m::Expect_command(1, true); + buildcc::m::TargetExpect_ExternalLibChanged(1, &exe); + buildcc::env::m::CommandExpect_Execute(1, true); exe.Build(); + buildcc::m::TargetRunner(exe); + CHECK_TRUE(exe.IsBuilt()); } mock().checkExpectations(); } int main(int ac, char **av) { - buildcc::env::init(BUILD_SCRIPT_SOURCE, - BUILD_TARGET_EXTERNAL_LIB_INTERMEDIATE_DIR); + buildcc::Project::Init(BUILD_SCRIPT_SOURCE, + BUILD_TARGET_EXTERNAL_LIB_INTERMEDIATE_DIR); return CommandLineTestRunner::RunAllTests(ac, av); } diff --git a/buildcc/lib/target/test/target/test_target_failure_states.cpp b/buildcc/lib/target/test/target/test_target_failure_states.cpp new file mode 100644 index 00000000..114ec908 --- /dev/null +++ b/buildcc/lib/target/test/target/test_target_failure_states.cpp @@ -0,0 +1,302 @@ +#include "constants.h" + +#include "env/logging.h" + +#include "expect_command.h" +#include "expect_target.h" + +#include "target/target.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(TargetTestFailureStates) +{ + void teardown() { + mock().checkExpectations(); + mock().clear(); + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + } +}; +// clang-format on + +static buildcc::Toolchain gcc(buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", + "ar", "ld")); + +TEST(TargetTestFailureStates, StartTaskEnvFailure) { + buildcc::env::set_task_state(buildcc::env::TaskState::FAILURE); + + constexpr const char *const NAME = "StartTaskEnvFailure.exe"; + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("dummy_main.cpp"); + target.Build(); + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); +} + +TEST(TargetTestFailureStates, CompilePchFailure) { + constexpr const char *const NAME = "CompilePchFailure.exe"; + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("dummy_main.cpp"); + target.AddPch("include/include_header.h"); + target.Build(); + + buildcc::env::m::CommandExpect_Execute(1, false); // PCH compile + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); +} + +TEST(TargetTestFailureStates, CompileObjectFailure) { + constexpr const char *const NAME = "CompileObjectFailure.exe"; + + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("dummy_main.cpp"); + target.AddSource("dummy_main.c"); + target.Build(); + + buildcc::env::m::CommandExpect_Execute(1, false); // compile + buildcc::env::m::CommandExpect_Execute(1, true); // compile + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); +} + +TEST(TargetTestFailureStates, CompileObject_FileNotFoundFailure) { + constexpr const char *const NAME = "CompileObject_FileNotFoundFailure.exe"; + + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("file_not_present.cpp"); + target.Build(); + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); +} + +TEST(TargetTestFailureStates, LinkTargetFailure) { + constexpr const char *const NAME = "LinkTargetFailure.exe"; + + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("dummy_main.cpp"); + target.Build(); + + buildcc::env::m::CommandExpect_Execute(1, true); // compile + buildcc::env::m::CommandExpect_Execute(1, false); // link + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); +} + +TEST(TargetTestFailureStates, EndTaskStoreFailure) { + constexpr const char *const NAME = "EndTaskStoreFailure.exe"; + + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("dummy_main.cpp"); + target.Build(); + fs::remove_all( + target.GetTargetBuildDir()); // removing this path causes store failure + + buildcc::env::m::CommandExpect_Execute(1, true); // compile + buildcc::env::m::CommandExpect_Execute(1, true); // link + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); +} + +// TODO, Test failure rebuilds! +// Every failure state during rebuild should re-run! + +TEST(TargetTestFailureStates, StartTaskEnvFailure_Rebuild) { + // env state is failure so target fails + buildcc::env::set_task_state(buildcc::env::TaskState::FAILURE); + constexpr const char *const NAME = "StartTaskEnvFailure_Rebuild.exe"; + + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("dummy_main.cpp"); + target.Build(); + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + CHECK_FALSE(target.IsBuilt()); + } + + // Reset + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + // during rebuild, this target must run! + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("dummy_main.cpp"); + target.Build(); + + buildcc::env::m::CommandExpect_Execute(1, true); // compile + buildcc::env::m::CommandExpect_Execute(1, true); // link + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + CHECK_TRUE(target.IsBuilt()); + } +} + +TEST(TargetTestFailureStates, CompilePchFailure_Rebuild) { + // Pch fails to compile during first run + constexpr const char *const NAME = "CompilePchFailure_Rebuild.exe"; + + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("dummy_main.cpp"); + target.AddPch("include/include_header.h"); + target.Build(); + + buildcc::env::m::CommandExpect_Execute(1, false); // PCH compile + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + } + + // Reset + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + // during rebuild, this must run! + // must move to compile object stage + // must move to link target stage + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("dummy_main.cpp"); + target.AddPch("include/include_header.h"); + target.Build(); + + buildcc::m::TargetExpect_PathAdded(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); // PCH compile + buildcc::env::m::CommandExpect_Execute(1, true); // Object compile + buildcc::env::m::CommandExpect_Execute(1, true); // Link target + + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + } +} + +TEST(TargetTestFailureStates, CompileObjectFailure_Rebuild) { + // Compile object fails during first run + + constexpr const char *const NAME = "CompileObjectFailure_Rebuild.exe"; + + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("dummy_main.cpp"); + target.AddSource("dummy_main.c"); + target.Build(); + + buildcc::env::m::CommandExpect_Execute(1, false); // compile + buildcc::env::m::CommandExpect_Execute(1, true); // compile + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + } + + // Reset + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + // during rebuild this must run the ones that were NOT build before + // for example + // a.cpp -> BUILT + // b.cpp -> FAILS + + // rerun + // a.cpp -> no need to compile again! + // b.cpp -> REBUILD + + // must move to link target stage + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("dummy_main.cpp"); + target.AddSource("dummy_main.c"); + target.Build(); + + // NOTE, The other one does not compile since it already compiled + // successfully earlier! + buildcc::env::m::CommandExpect_Execute(1, true); // compile + buildcc::m::TargetExpect_SourceAdded(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); // link + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + } +} + +TEST(TargetTestFailureStates, LinkTargetFailure_Rebuild) { + // Link target fails during first run + constexpr const char *const NAME = "LinkTargetFailure_Rebuild.exe"; + + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("dummy_main.cpp"); + target.Build(); + + buildcc::env::m::CommandExpect_Execute(1, true); // compile + buildcc::env::m::CommandExpect_Execute(1, false); // link + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + } + + // Reset + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + // during rebuild this must try to relink! + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + + target.AddSource("dummy_main.cpp"); + target.Build(); + + // we do not recompile + buildcc::env::m::CommandExpect_Execute(1, true); // link + buildcc::m::TargetRunner(target); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + } +} + +int main(int ac, char **av) { + buildcc::Project::Init(BUILD_SCRIPT_SOURCE, + BUILD_TARGET_FAILURE_STATES_BUILD_DIR); + fs::remove_all(buildcc::Project::GetBuildDir()); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/target/test/target/test_target_flags.cpp b/buildcc/lib/target/test/target/test_target_flags.cpp new file mode 100644 index 00000000..af1d1b91 --- /dev/null +++ b/buildcc/lib/target/test/target/test_target_flags.cpp @@ -0,0 +1,708 @@ +#include "constants.h" + +#include "expect_command.h" +#include "expect_target.h" + +#include "target/target.h" + +#include "env/env.h" + +// +#include "schema/target_serialization.h" + +// Third Party + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// Constants + +static buildcc::Toolchain gcc(buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", + "ar", "ld")); + +// ------------- PREPROCESSOR FLAGS --------------- + +// clang-format off +TEST_GROUP(TargetTestPreprocessorFlagGroup) +{ + void teardown() { + mock().checkExpectations(); + mock().clear(); + } +}; +// clang-format on + +static const fs::path target_preprocessorflag_intermediate_path = + fs::path(BUILD_TARGET_FLAG_INTERMEDIATE_DIR) / gcc.GetName(); + +TEST(TargetTestPreprocessorFlagGroup, Target_AddPreprocessorFlag) { + constexpr const char *const NAME = "AddPreprocessorFlag.exe"; + constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; + + auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; + auto intermediate_path = target_preprocessorflag_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddPreprocessorFlag("-DCOMPILE=1"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + + mock().checkExpectations(); + + // Verify binary + buildcc::internal::TargetSerialization serialization( + simple.GetTargetBuildDir() / (std::string(NAME) + ".bin")); + bool loaded = serialization.LoadFromFile(); + CHECK_TRUE(loaded); + + CHECK_EQUAL(serialization.GetLoad().preprocessor_flags.size(), 1); +} + +TEST(TargetTestPreprocessorFlagGroup, Target_ChangedPreprocessorFlag) { + constexpr const char *const NAME = "ChangedPreprocessorFlag.exe"; + constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; + + auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; + auto intermediate_path = target_preprocessorflag_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + + { + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddPreprocessorFlag("-DCOMPILE=1"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + { + // * Remove flag + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + + { + // * Add flag + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddPreprocessorFlag("-DRANDOM=1"); + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + + mock().checkExpectations(); +} + +// ------------- COMMON COMPILE FLAGS --------------- + +// clang-format off +TEST_GROUP(TargetTestCommonCompileFlagsGroup) +{ + void teardown() { + mock().clear(); + } +}; +// clang-format on + +static const fs::path target_commonflag_intermediate_path = + fs::path(BUILD_TARGET_FLAG_INTERMEDIATE_DIR) / gcc.GetName(); + +TEST(TargetTestCommonCompileFlagsGroup, Target_AddCommonCompileFlag) { + constexpr const char *const NAME = "AddCommonCompileFlag.exe"; + constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; + + auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; + auto intermediate_path = target_commonflag_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + + { + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddCommonCompileFlag("-O0"); + simple.AddCommonCompileFlag("-g"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + + mock().checkExpectations(); + + // Verify binary + buildcc::internal::TargetSerialization serialization( + simple.GetTargetBuildDir() / (std::string(NAME) + ".bin")); + bool loaded = serialization.LoadFromFile(); + CHECK_TRUE(loaded); + + CHECK_EQUAL(serialization.GetLoad().common_compile_flags.size(), 2); + } + + // Trigger Rebuild for Common Compile flag + { + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddCommonCompileFlag("-O0"); + + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + + mock().checkExpectations(); + + // Verify binary + buildcc::internal::TargetSerialization serialization( + simple.GetTargetBuildDir() / (std::string(NAME) + ".bin")); + bool loaded = serialization.LoadFromFile(); + CHECK_TRUE(loaded); + + CHECK_EQUAL(serialization.GetLoad().common_compile_flags.size(), 1); + } +} + +TEST(TargetTestCommonCompileFlagsGroup, Target_ChangedCommonCompileFlag) { + constexpr const char *const NAME = "ChangedCommonCompileFlag.exe"; + constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; + + auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; + auto intermediate_path = target_commonflag_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + + { + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddCommonCompileFlag("-O0"); + simple.AddCommonCompileFlag("-g"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + { + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddCommonCompileFlag("-O0"); + simple.AddCommonCompileFlag("-g"); + + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_FALSE(simple.IsBuilt()); + } + { + // * Remove flag + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddCommonCompileFlag("-O0"); + + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + + { + // * Add flag + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddCommonCompileFlag("-O0"); + simple.AddCommonCompileFlag("-g"); + + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + + mock().checkExpectations(); +} + +// ------------- ASM COMPILE FLAGS --------------- + +// clang-format off +TEST_GROUP(TargetTestAsmCompileFlagGroup) +{ + void teardown() { + mock().clear(); + } +}; +// clang-format on + +static const fs::path target_asmflag_intermediate_path = + fs::path(BUILD_TARGET_FLAG_INTERMEDIATE_DIR) / gcc.GetName(); + +TEST(TargetTestAsmCompileFlagGroup, Target_AddCompileFlag) { + constexpr const char *const NAME = "AddAsmCompileFlag.exe"; + constexpr const char *const EMPTY_ASM = "asm/empty_asm.s"; + + auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; + auto intermediate_path = target_asmflag_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(EMPTY_ASM); + simple.AddAsmCompileFlag("-O0"); + simple.AddAsmCompileFlag("-g"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + + mock().checkExpectations(); + + // Verify binary + buildcc::internal::TargetSerialization serialization( + simple.GetTargetBuildDir() / (std::string(NAME) + ".bin")); + bool loaded = serialization.LoadFromFile(); + CHECK_TRUE(loaded); + + CHECK_EQUAL(serialization.GetLoad().asm_compile_flags.size(), 2); +} + +TEST(TargetTestAsmCompileFlagGroup, Target_ChangedCompileFlag) { + constexpr const char *const NAME = "ChangedAsmCompileFlag.exe"; + constexpr const char *const EMPTY_ASM = "asm/empty_asm.s"; + + auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; + auto intermediate_path = target_asmflag_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + + { + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(EMPTY_ASM); + simple.AddAsmCompileFlag("-O0"); + simple.AddAsmCompileFlag("-g"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + { + // * No Change + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(EMPTY_ASM); + simple.AddAsmCompileFlag("-O0"); + simple.AddAsmCompileFlag("-g"); + + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_FALSE(simple.IsBuilt()); + } + { + // * Remove flag + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(EMPTY_ASM); + simple.AddAsmCompileFlag("-O0"); + + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + + { + // * Add flag + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(EMPTY_ASM); + simple.AddAsmCompileFlag("-O0"); + simple.AddAsmCompileFlag("-g"); + + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + + mock().checkExpectations(); +} + +// ------------- C COMPILE FLAGS --------------- + +// clang-format off +TEST_GROUP(TargetTestCCompileFlagsGroup) +{ + void teardown() { + mock().clear(); + } +}; +// clang-format on + +static const fs::path target_cflag_intermediate_path = + fs::path(BUILD_TARGET_FLAG_INTERMEDIATE_DIR) / gcc.GetName(); + +TEST(TargetTestCCompileFlagsGroup, Target_AddCompileFlag) { + constexpr const char *const NAME = "AddCCompileFlag.exe"; + constexpr const char *const DUMMY_MAIN = "dummy_main.c"; + + auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; + auto intermediate_path = target_cflag_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddCCompileFlag("-std=c11"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + + mock().checkExpectations(); + + // Verify binary + buildcc::internal::TargetSerialization serialization( + simple.GetTargetBuildDir() / (std::string(NAME) + ".bin")); + bool loaded = serialization.LoadFromFile(); + CHECK_TRUE(loaded); + + CHECK_EQUAL(serialization.GetLoad().c_compile_flags.size(), 1); +} + +TEST(TargetTestCCompileFlagsGroup, Target_ChangedCompileFlag) { + constexpr const char *const NAME = "ChangedCCompileFlag.exe"; + constexpr const char *const DUMMY_MAIN = "dummy_main.c"; + + auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; + auto intermediate_path = target_cflag_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + + { + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddCCompileFlag("-std=c11"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + { + // * Remove flag + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + + { + // * Add flag + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddCCompileFlag("-std=c11"); + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + + mock().checkExpectations(); +} + +// ------------- CPP COMPILE FLAGS --------------- + +// clang-format off +TEST_GROUP(TargetTestCppCompileFlagsGroup) +{ + void teardown() { + mock().clear(); + } +}; +// clang-format on + +static const fs::path target_cppflags_intermediate_path = + fs::path(BUILD_TARGET_FLAG_INTERMEDIATE_DIR) / gcc.GetName(); + +TEST(TargetTestCppCompileFlagsGroup, Target_AddCompileFlag) { + constexpr const char *const NAME = "AddCppCompileFlag.exe"; + constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; + + auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; + auto intermediate_path = target_cppflags_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + + { + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddCppCompileFlag("-std=c++17"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + + mock().checkExpectations(); + + // Verify binary + buildcc::internal::TargetSerialization serialization( + simple.GetTargetBuildDir() / (std::string(NAME) + ".bin")); + bool loaded = serialization.LoadFromFile(); + CHECK_TRUE(loaded); + + CHECK_EQUAL(serialization.GetLoad().cpp_compile_flags.size(), 1); + } + + // Trigger rebuild for Cpp Compile flag + { + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddCppCompileFlag("-std=c++20"); + + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + + mock().checkExpectations(); + + // Verify binary + buildcc::internal::TargetSerialization serialization( + simple.GetTargetBuildDir() / (std::string(NAME) + ".bin")); + bool loaded = serialization.LoadFromFile(); + CHECK_TRUE(loaded); + + CHECK_EQUAL(serialization.GetLoad().cpp_compile_flags.size(), 1); + } +} + +TEST(TargetTestCppCompileFlagsGroup, Target_ChangedCompileFlag) { + constexpr const char *const NAME = "ChangedCppCompileFlag.exe"; + constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; + + auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; + auto intermediate_path = target_cppflags_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + + { + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddCCompileFlag("-std=c++17"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + { + // * Remove flag + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + + { + // * Add flag + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddCCompileFlag("-std=c++17"); + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + + mock().checkExpectations(); +} + +// ------------- LINK FLAGS --------------- + +// clang-format off +TEST_GROUP(TargetTestLinkFlagsGroup) +{ + void teardown() { + mock().clear(); + } +}; +// clang-format on + +static const fs::path target_linkflag_intermediate_path = + fs::path(BUILD_TARGET_FLAG_INTERMEDIATE_DIR) / gcc.GetName(); + +TEST(TargetTestLinkFlagsGroup, Target_AddLinkFlag) { + constexpr const char *const NAME = "AddLinkFlag.exe"; + constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; + + auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; + auto intermediate_path = target_linkflag_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddLinkFlag("-lm"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + + mock().checkExpectations(); + + // Verify binary + buildcc::internal::TargetSerialization serialization( + simple.GetTargetBuildDir() / (std::string(NAME) + ".bin")); + bool loaded = serialization.LoadFromFile(); + CHECK_TRUE(loaded); + + CHECK_EQUAL(serialization.GetLoad().link_flags.size(), 1); +} + +TEST(TargetTestLinkFlagsGroup, Target_ChangedLinkFlag) { + constexpr const char *const NAME = "ChangedLinkFlag.exe"; + constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; + + auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; + auto intermediate_path = target_linkflag_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + + { + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddLinkFlag("-lm"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + { + // * Remove flag + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + + { + // * Add flag + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource(DUMMY_MAIN); + simple.AddLinkFlag("-lm"); + buildcc::m::TargetExpect_FlagChanged(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + CHECK_TRUE(simple.IsBuilt()); + } + + mock().checkExpectations(); +} + +int main(int ac, char **av) { + buildcc::Project::Init(BUILD_SCRIPT_SOURCE, + BUILD_TARGET_FLAG_INTERMEDIATE_DIR); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/target/test/target/test_target_include_dir.cpp b/buildcc/lib/target/test/target/test_target_include_dir.cpp index 18b5a890..2636f52e 100644 --- a/buildcc/lib/target/test/target/test_target_include_dir.cpp +++ b/buildcc/lib/target/test/target/test_target_include_dir.cpp @@ -1,12 +1,14 @@ #include "constants.h" +#include "expect_command.h" #include "expect_target.h" -#include "target.h" -#include "env.h" +#include "target/target.h" + +#include "env/env.h" +#include "env/util.h" // Third Party -#include "flatbuffers/util.h" // NOTE, Make sure all these includes are AFTER the system and header includes #include "CppUTest/CommandLineTestRunner.h" @@ -24,21 +26,23 @@ TEST_GROUP(TargetTestIncludeDirGroup) }; // clang-format on -static const buildcc::base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", - "ld"); +static buildcc::Toolchain gcc(buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", + "ar", "ld")); + static const fs::path target_include_dir_intermediate_path = fs::path(BUILD_TARGET_INCLUDE_DIR_INTERMEDIATE_DIR) / gcc.GetName(); TEST(TargetTestIncludeDirGroup, Target_HeaderTypes) { constexpr const char *const NAME = "HeaderTypes.exe"; auto intermediate_path = target_include_dir_intermediate_path / NAME; - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, gcc, - "data"); + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); simple.AddHeader("fileext/header_file1.h"); simple.AddHeader("fileext/header_file2.hpp"); - CHECK_EQUAL(simple.GetCurrentHeaderFiles().size(), 2); + CHECK_EQUAL(simple.GetHeaderFiles().size(), 2); CHECK_THROWS(std::exception, simple.AddHeader("fileext/c_file.c")); CHECK_THROWS(std::exception, simple.AddHeader("fileext/cpp_file1.cpp")); @@ -57,11 +61,11 @@ TEST(TargetTestIncludeDirGroup, TargetGlobHeader) { // Delete fs::remove_all(intermediate_path); - buildcc::base::Target globHeader(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); + buildcc::BaseTarget globHeader(NAME, buildcc::TargetType::Executable, gcc, + "data"); globHeader.GlobHeaders("include"); globHeader.GlobHeaders(""); - CHECK_EQUAL(globHeader.GetCurrentHeaderFiles().size(), 1); + CHECK_EQUAL(globHeader.GetHeaderFiles().size(), 1); } TEST(TargetTestIncludeDirGroup, TargetGlobThroughIncludeDir) { @@ -70,11 +74,11 @@ TEST(TargetTestIncludeDirGroup, TargetGlobThroughIncludeDir) { // Delete fs::remove_all(intermediate_path); - buildcc::base::Target globIncludeDir( - NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget globIncludeDir(NAME, buildcc::TargetType::Executable, gcc, + "data"); globIncludeDir.AddIncludeDir("include", true); globIncludeDir.AddIncludeDir("", true); - CHECK_EQUAL(globIncludeDir.GetCurrentHeaderFiles().size(), 1); + CHECK_EQUAL(globIncludeDir.GetHeaderFiles().size(), 1); } TEST(TargetTestIncludeDirGroup, TargetBuildIncludeDir) { @@ -90,62 +94,72 @@ TEST(TargetTestIncludeDirGroup, TargetBuildIncludeDir) { // Delete fs::remove_all(intermediate_path); - auto dummy_c_file = - buildcc::internal::Path::CreateExistingPath((source_path / DUMMY_MAIN_C)); - auto include_header_file = buildcc::internal::Path::CreateExistingPath( - (source_path / INCLUDE_HEADER_SOURCE)); - auto include_header_path = - (source_path / RELATIVE_INCLUDE_DIR).make_preferred(); + auto dummy_c_file = buildcc::internal::PathInfo::ToPathString( + fs::path(source_path / DUMMY_MAIN_C).string()); + + auto include_header_file = buildcc::internal::PathInfo::ToPathString( + fs::path(source_path / INCLUDE_HEADER_SOURCE).string()); + + auto include_header_path = buildcc::internal::PathInfo::ToPathString( + fs::path(source_path / RELATIVE_INCLUDE_DIR).string()); { - buildcc::base::Target include_compile( - NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget include_compile(NAME, buildcc::TargetType::Executable, + gcc, "data"); include_compile.AddSource(DUMMY_MAIN_C); include_compile.AddSource(INCLUDE_HEADER_SOURCE); include_compile.AddIncludeDir(RELATIVE_INCLUDE_DIR); - // Duplicate include directory + // Duplicate include directory (will be reflected) include_compile.AddIncludeDir(RELATIVE_INCLUDE_DIR); - buildcc::internal::m::Expect_command(2, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(2, true); + buildcc::env::m::CommandExpect_Execute(1, true); include_compile.Build(); + buildcc::m::TargetRunner(include_compile); - buildcc::internal::FbsLoader loader(NAME, intermediate_path); - bool is_loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization( + intermediate_path / (std::string(NAME) + ".bin")); + bool is_loaded = serialization.LoadFromFile(); CHECK_TRUE(is_loaded); - const auto &loaded_sources = loader.GetLoadedSources(); - const auto &loaded_dirs = loader.GetLoadedIncludeDirs(); + const auto &loaded_sources = + serialization.GetLoad().sources.GetUnorderedPathInfos(); + const auto &loaded_dirs = serialization.GetLoad().include_dirs.GetPaths(); CHECK_EQUAL(loaded_sources.size(), 2); - CHECK_EQUAL(loaded_dirs.size(), 1); + CHECK_EQUAL(loaded_dirs.size(), 2); CHECK_FALSE(loaded_sources.find(dummy_c_file) == loaded_sources.end()); CHECK_FALSE(loaded_sources.find(include_header_file) == loaded_sources.end()); - CHECK_FALSE(loaded_dirs.find(include_header_path.string()) == - loaded_dirs.end()); + std::unordered_set unordered_loaded_dirs(loaded_dirs.begin(), + loaded_dirs.end()); + CHECK_FALSE(unordered_loaded_dirs.find(include_header_path) == + unordered_loaded_dirs.end()); } { // * 1 Adding new include directory - buildcc::base::Target include_compile( - NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget include_compile(NAME, buildcc::TargetType::Executable, + gcc, "data"); include_compile.AddSource(DUMMY_MAIN_C); include_compile.AddSource(INCLUDE_HEADER_SOURCE); include_compile.AddIncludeDir(RELATIVE_INCLUDE_DIR); // Adds the data directory include_compile.AddIncludeDir(""); - buildcc::base::m::TargetExpect_DirChanged(1, &include_compile); - buildcc::internal::m::Expect_command(2, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::m::TargetExpect_DirChanged(1, &include_compile); + buildcc::env::m::CommandExpect_Execute(2, true); + buildcc::env::m::CommandExpect_Execute(1, true); include_compile.Build(); + buildcc::m::TargetRunner(include_compile); - buildcc::internal::FbsLoader loader(NAME, intermediate_path); - bool is_loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization( + intermediate_path / (std::string(NAME) + ".bin")); + bool is_loaded = serialization.LoadFromFile(); CHECK_TRUE(is_loaded); - const auto &loaded_sources = loader.GetLoadedSources(); - const auto &loaded_dirs = loader.GetLoadedIncludeDirs(); + const auto &loaded_sources = + serialization.GetLoad().sources.GetUnorderedPathInfos(); + const auto &loaded_dirs = serialization.GetLoad().include_dirs.GetPaths(); CHECK_EQUAL(loaded_sources.size(), 2); CHECK_EQUAL(loaded_dirs.size(), 2); @@ -153,27 +167,33 @@ TEST(TargetTestIncludeDirGroup, TargetBuildIncludeDir) { CHECK_FALSE(loaded_sources.find(dummy_c_file) == loaded_sources.end()); CHECK_FALSE(loaded_sources.find(include_header_file) == loaded_sources.end()); - CHECK_FALSE(loaded_dirs.find(include_header_path.string()) == - loaded_dirs.end()); + + std::unordered_set unordered_loaded_dirs(loaded_dirs.begin(), + loaded_dirs.end()); + CHECK_FALSE(unordered_loaded_dirs.find(include_header_path) == + unordered_loaded_dirs.end()); } { // * Remove include directory - buildcc::base::Target include_compile( - NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget include_compile(NAME, buildcc::TargetType::Executable, + gcc, "data"); include_compile.AddSource(DUMMY_MAIN_C); include_compile.AddSource(INCLUDE_HEADER_SOURCE); include_compile.AddIncludeDir(RELATIVE_INCLUDE_DIR); - buildcc::base::m::TargetExpect_DirChanged(1, &include_compile); - buildcc::internal::m::Expect_command(2, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::m::TargetExpect_DirChanged(1, &include_compile); + buildcc::env::m::CommandExpect_Execute(2, true); + buildcc::env::m::CommandExpect_Execute(1, true); include_compile.Build(); + buildcc::m::TargetRunner(include_compile); - buildcc::internal::FbsLoader loader(NAME, intermediate_path); - bool is_loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization( + intermediate_path / (std::string(NAME) + ".bin")); + bool is_loaded = serialization.LoadFromFile(); CHECK_TRUE(is_loaded); - const auto &loaded_sources = loader.GetLoadedSources(); - const auto &loaded_dirs = loader.GetLoadedIncludeDirs(); + const auto &loaded_sources = + serialization.GetLoad().sources.GetUnorderedPathInfos(); + const auto &loaded_dirs = serialization.GetLoad().include_dirs.GetPaths(); CHECK_EQUAL(loaded_sources.size(), 2); CHECK_EQUAL(loaded_dirs.size(), 1); @@ -181,8 +201,11 @@ TEST(TargetTestIncludeDirGroup, TargetBuildIncludeDir) { CHECK_FALSE(loaded_sources.find(dummy_c_file) == loaded_sources.end()); CHECK_FALSE(loaded_sources.find(include_header_file) == loaded_sources.end()); - CHECK_FALSE(loaded_dirs.find(include_header_path.string()) == - loaded_dirs.end()); + + std::unordered_set unordered_loaded_dirs(loaded_dirs.begin(), + loaded_dirs.end()); + CHECK_FALSE(unordered_loaded_dirs.find(include_header_path) == + unordered_loaded_dirs.end()); } mock().checkExpectations(); @@ -202,53 +225,55 @@ TEST(TargetTestIncludeDirGroup, TargetBuildHeaderFile) { // Delete fs::remove_all(intermediate_path); - auto dummy_c_file = - buildcc::internal::Path::CreateExistingPath((source_path / DUMMY_MAIN_C)); - auto include_header_file = buildcc::internal::Path::CreateExistingPath( - (source_path / INCLUDE_HEADER_SOURCE)); auto include_header_path = (source_path / RELATIVE_INCLUDE_DIR).make_preferred(); // Initial build { - buildcc::base::Target add_header( - NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget add_header(NAME, buildcc::TargetType::Executable, gcc, + "data"); add_header.AddSource(DUMMY_MAIN_C); add_header.AddSource(INCLUDE_HEADER_SOURCE); add_header.AddIncludeDir(RELATIVE_INCLUDE_DIR); - buildcc::internal::m::Expect_command(2, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(2, true); + buildcc::env::m::CommandExpect_Execute(1, true); add_header.Build(); + buildcc::m::TargetRunner(add_header); - buildcc::internal::FbsLoader loader(NAME, intermediate_path); - bool is_loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization( + intermediate_path / (std::string(NAME) + ".bin")); + bool is_loaded = serialization.LoadFromFile(); CHECK_TRUE(is_loaded); - CHECK_EQUAL(loader.GetLoadedSources().size(), 2); - CHECK_EQUAL(loader.GetLoadedIncludeDirs().size(), 1); - CHECK_EQUAL(loader.GetLoadedHeaders().size(), 0); + CHECK_EQUAL(serialization.GetLoad().sources.GetUnorderedPathInfos().size(), + 2); + CHECK_EQUAL(serialization.GetLoad().include_dirs.GetPaths().size(), 1); + CHECK_EQUAL(serialization.GetLoad().headers.GetPaths().size(), 0); } // Add header { - buildcc::base::Target add_header( - NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget add_header(NAME, buildcc::TargetType::Executable, gcc, + "data"); add_header.AddSource(DUMMY_MAIN_C); add_header.AddSource(INCLUDE_HEADER_SOURCE); add_header.AddHeader(RELATIVE_HEADER_FILE); add_header.AddIncludeDir(RELATIVE_INCLUDE_DIR); - buildcc::base::m::TargetExpect_PathAdded(1, &add_header); - buildcc::internal::m::Expect_command(2, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::m::TargetExpect_PathAdded(1, &add_header); + buildcc::env::m::CommandExpect_Execute(2, true); + buildcc::env::m::CommandExpect_Execute(1, true); add_header.Build(); + buildcc::m::TargetRunner(add_header); - buildcc::internal::FbsLoader loader(NAME, intermediate_path); - bool is_loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization( + intermediate_path / (std::string(NAME) + ".bin")); + bool is_loaded = serialization.LoadFromFile(); CHECK_TRUE(is_loaded); - CHECK_EQUAL(loader.GetLoadedSources().size(), 2); - CHECK_EQUAL(loader.GetLoadedIncludeDirs().size(), 1); - CHECK_EQUAL(loader.GetLoadedHeaders().size(), 1); + CHECK_EQUAL(serialization.GetLoad().sources.GetUnorderedPathInfos().size(), + 2); + CHECK_EQUAL(serialization.GetLoad().include_dirs.GetPaths().size(), 1); + CHECK_EQUAL(serialization.GetLoad().headers.GetPaths().size(), 1); } // Update header @@ -256,55 +281,61 @@ TEST(TargetTestIncludeDirGroup, TargetBuildHeaderFile) { { const fs::path absolute_header_path = fs::path(BUILD_SCRIPT_SOURCE) / "data" / RELATIVE_HEADER_FILE; - flatbuffers::SaveFile(absolute_header_path.string().c_str(), - std::string{""}, false); + buildcc::env::save_file(absolute_header_path.string().c_str(), + std::string{""}, false); - buildcc::base::Target add_header( - NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget add_header(NAME, buildcc::TargetType::Executable, gcc, + "data"); add_header.AddSource(DUMMY_MAIN_C); add_header.AddSource(INCLUDE_HEADER_SOURCE); add_header.AddHeader(RELATIVE_HEADER_FILE); add_header.AddIncludeDir(RELATIVE_INCLUDE_DIR); - buildcc::base::m::TargetExpect_PathUpdated(1, &add_header); - buildcc::internal::m::Expect_command(2, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::m::TargetExpect_PathUpdated(1, &add_header); + buildcc::env::m::CommandExpect_Execute(2, true); + buildcc::env::m::CommandExpect_Execute(1, true); add_header.Build(); + buildcc::m::TargetRunner(add_header); - buildcc::internal::FbsLoader loader(NAME, intermediate_path); - bool is_loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization( + intermediate_path / (std::string(NAME) + ".bin")); + bool is_loaded = serialization.LoadFromFile(); CHECK_TRUE(is_loaded); - CHECK_EQUAL(loader.GetLoadedSources().size(), 2); - CHECK_EQUAL(loader.GetLoadedIncludeDirs().size(), 1); - CHECK_EQUAL(loader.GetLoadedHeaders().size(), 1); + CHECK_EQUAL(serialization.GetLoad().sources.GetUnorderedPathInfos().size(), + 2); + CHECK_EQUAL(serialization.GetLoad().include_dirs.GetPaths().size(), 1); + CHECK_EQUAL(serialization.GetLoad().headers.GetPaths().size(), 1); } // Remove header { - buildcc::base::Target add_header( - NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget add_header(NAME, buildcc::TargetType::Executable, gcc, + "data"); add_header.AddSource(DUMMY_MAIN_C); add_header.AddSource(INCLUDE_HEADER_SOURCE); add_header.AddIncludeDir(RELATIVE_INCLUDE_DIR); - buildcc::base::m::TargetExpect_PathRemoved(1, &add_header); - buildcc::internal::m::Expect_command(2, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::m::TargetExpect_PathRemoved(1, &add_header); + buildcc::env::m::CommandExpect_Execute(2, true); + buildcc::env::m::CommandExpect_Execute(1, true); add_header.Build(); + buildcc::m::TargetRunner(add_header); - buildcc::internal::FbsLoader loader(NAME, intermediate_path); - bool is_loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization( + intermediate_path / (std::string(NAME) + ".bin")); + bool is_loaded = serialization.LoadFromFile(); CHECK_TRUE(is_loaded); - CHECK_EQUAL(loader.GetLoadedSources().size(), 2); - CHECK_EQUAL(loader.GetLoadedIncludeDirs().size(), 1); - CHECK_EQUAL(loader.GetLoadedHeaders().size(), 0); + CHECK_EQUAL(serialization.GetLoad().sources.GetUnorderedPathInfos().size(), + 2); + CHECK_EQUAL(serialization.GetLoad().include_dirs.GetPaths().size(), 1); + CHECK_EQUAL(serialization.GetLoad().headers.GetPaths().size(), 0); } mock().checkExpectations(); } int main(int ac, char **av) { - buildcc::env::init(BUILD_SCRIPT_SOURCE, - BUILD_TARGET_INCLUDE_DIR_INTERMEDIATE_DIR); + buildcc::Project::Init(BUILD_SCRIPT_SOURCE, + BUILD_TARGET_INCLUDE_DIR_INTERMEDIATE_DIR); return CommandLineTestRunner::RunAllTests(ac, av); } diff --git a/buildcc/lib/target/test/target/test_target_lib_dep.cpp b/buildcc/lib/target/test/target/test_target_lib_dep.cpp index 7bc43f67..21078e7d 100644 --- a/buildcc/lib/target/test/target/test_target_lib_dep.cpp +++ b/buildcc/lib/target/test/target/test_target_lib_dep.cpp @@ -1,17 +1,18 @@ #include "constants.h" -#include "logging.h" +#include "env/logging.h" +#include "env/util.h" +#include "expect_command.h" #include "expect_target.h" -#include "target.h" +#include "test_target_util.h" -#include "flatbuffers/util.h" +#include "target/target.h" // -#include "internal/fbs_loader.h" +#include "schema/target_serialization.h" #include -#include // NOTE, Make sure all these includes are AFTER the system and header includes #include "CppUTest/CommandLineTestRunner.h" @@ -29,8 +30,10 @@ TEST_GROUP(TargetTestLibDep) }; // clang-format on -static const buildcc::base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", - "ld"); +static buildcc::Toolchain gcc(buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", + "ar", "ld")); + static const fs::path intermediate_path = fs::path(BUILD_TARGET_LIB_DEP_INTERMEDIATE_DIR) / gcc.GetName(); @@ -39,25 +42,26 @@ TEST(TargetTestLibDep, StaticLibrary_SimpleBuildTest) { fs::remove_all(intermediate_path / STATIC_NAME); - buildcc::base::Target foolib( - STATIC_NAME, buildcc::base::TargetType::StaticLibrary, gcc, "data"); + buildcc::BaseTarget foolib(STATIC_NAME, buildcc::TargetType::StaticLibrary, + gcc, "data"); foolib.AddSource("foo.cpp", "foo"); foolib.AddIncludeDir("foo"); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); foolib.Build(); + buildcc::m::TargetRunner(foolib); mock().checkExpectations(); // Verify binary - buildcc::internal::FbsLoader loader(STATIC_NAME, - foolib.GetTargetIntermediateDir()); - bool loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization( + foolib.GetTargetBuildDir() / (std::string(STATIC_NAME) + ".bin")); + bool loaded = serialization.LoadFromFile(); CHECK_TRUE(loaded); - CHECK_EQUAL(loader.GetLoadedSources().size(), 1); - CHECK_EQUAL(loader.GetLoadedIncludeDirs().size(), 1); + CHECK_EQUAL(serialization.GetLoad().sources.GetPathInfos().size(), 1); + CHECK_EQUAL(serialization.GetLoad().include_dirs.GetPaths().size(), 1); } TEST(TargetTestLibDep, TargetDep_RebuildTest) { @@ -68,43 +72,49 @@ TEST(TargetTestLibDep, TargetDep_RebuildTest) { fs::remove_all(intermediate_path / EXE_NAME); { - buildcc::base::Target foolib( - STATIC_FOO_LIB, buildcc::base::TargetType::StaticLibrary, gcc, "data"); + buildcc::BaseTarget foolib(STATIC_FOO_LIB, + buildcc::TargetType::StaticLibrary, gcc, "data"); foolib.AddSource("foo/foo.cpp"); foolib.AddIncludeDir("foo"); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); foolib.Build(); - flatbuffers::SaveFile(foolib.GetTargetPath().string().c_str(), - std::string{""}, false); + buildcc::m::TargetRunner(foolib); + buildcc::env::save_file(foolib.GetTargetPath().string().c_str(), + std::string{""}, false); // Executable for static - buildcc::base::Target exe_target( - EXE_NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget exe_target(EXE_NAME, buildcc::TargetType::Executable, + gcc, "data"); exe_target.AddSource("foo_main.cpp"); exe_target.AddIncludeDir("foo"); exe_target.AddLibDep(foolib); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); exe_target.Build(); + buildcc::m::TargetRunner(exe_target); } { - buildcc::base::Target foolib( - STATIC_FOO_LIB, buildcc::base::TargetType::StaticLibrary, gcc, "data"); + buildcc::BaseTarget foolib(STATIC_FOO_LIB, + buildcc::TargetType::StaticLibrary, gcc, "data"); foolib.AddSource("foo/foo.cpp"); foolib.AddIncludeDir("foo"); foolib.Build(); + buildcc::m::TargetRunner(foolib); + CHECK_FALSE(foolib.IsBuilt()); // Executable for static - buildcc::base::Target exe_target( - EXE_NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget exe_target(EXE_NAME, buildcc::TargetType::Executable, + gcc, "data"); exe_target.AddSource("foo_main.cpp"); exe_target.AddIncludeDir("foo"); exe_target.AddLibDep(foolib); exe_target.Build(); + buildcc::m::TargetRunner(exe_target); + CHECK_FALSE(exe_target.IsBuilt()); } mock().checkExpectations(); @@ -117,55 +127,59 @@ TEST(TargetTestLibDep, TargetDep_AddRemoveTest) { fs::remove_all(intermediate_path / STATIC_NAME); fs::remove_all(intermediate_path / EXE_NAME); - buildcc::base::Target foolib( - STATIC_NAME, buildcc::base::TargetType::StaticLibrary, gcc, "data"); + buildcc::BaseTarget foolib(STATIC_NAME, buildcc::TargetType::StaticLibrary, + gcc, "data"); foolib.AddSource("foo/foo.cpp"); foolib.AddIncludeDir("foo"); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); foolib.Build(); + buildcc::m::TargetRunner(foolib); - flatbuffers::SaveFile(foolib.GetTargetPath().string().c_str(), - std::string{""}, false); + buildcc::env::save_file(foolib.GetTargetPath().string().c_str(), + std::string{""}, false); // * Initial executable // Executable for static { - buildcc::base::Target exe_target( - EXE_NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget exe_target(EXE_NAME, buildcc::TargetType::Executable, + gcc, "data"); exe_target.AddSource("empty_main.cpp"); exe_target.AddIncludeDir("foo"); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); exe_target.Build(); + buildcc::m::TargetRunner(exe_target); } // * Add new library // Build { - buildcc::base::Target exe_target( - EXE_NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget exe_target(EXE_NAME, buildcc::TargetType::Executable, + gcc, "data"); exe_target.AddSource("empty_main.cpp"); exe_target.AddIncludeDir("foo"); exe_target.AddLibDep(foolib); - buildcc::base::m::TargetExpect_PathAdded(1, &exe_target); - buildcc::internal::m::Expect_command(1, true); + buildcc::m::TargetExpect_PathAdded(1, &exe_target); + buildcc::env::m::CommandExpect_Execute(1, true); exe_target.Build(); + buildcc::m::TargetRunner(exe_target); } // * Remove library { - buildcc::base::Target exe_target( - EXE_NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget exe_target(EXE_NAME, buildcc::TargetType::Executable, + gcc, "data"); exe_target.AddSource("empty_main.cpp"); exe_target.AddIncludeDir("foo"); - buildcc::base::m::TargetExpect_PathRemoved(1, &exe_target); - buildcc::internal::m::Expect_command(1, true); + buildcc::m::TargetExpect_PathRemoved(1, &exe_target); + buildcc::env::m::CommandExpect_Execute(1, true); exe_target.Build(); + buildcc::m::TargetRunner(exe_target); } mock().checkExpectations(); @@ -180,65 +194,69 @@ TEST(TargetTestLibDep, TargetDep_UpdateExistingLibraryTest) { // Build initial { - buildcc::base::Target foolib( - STATIC_NAME, buildcc::base::TargetType::StaticLibrary, gcc, "data"); + buildcc::BaseTarget foolib(STATIC_NAME, buildcc::TargetType::StaticLibrary, + gcc, "data"); foolib.AddSource("foo/foo.cpp"); foolib.AddIncludeDir("foo"); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); foolib.Build(); + buildcc::m::TargetRunner(foolib); - bool saved = flatbuffers::SaveFile(foolib.GetTargetPath().string().c_str(), - std::string{""}, false); + bool saved = buildcc::env::save_file( + foolib.GetTargetPath().string().c_str(), std::string{""}, false); CHECK_TRUE(saved); - buildcc::base::Target exe_target( - EXE_NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget exe_target(EXE_NAME, buildcc::TargetType::Executable, + gcc, "data"); exe_target.AddSource("foo_main.cpp"); exe_target.AddIncludeDir("foo"); exe_target.AddLibDep(foolib); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); exe_target.Build(); + buildcc::m::TargetRunner(exe_target); } // * Update static library { - buildcc::base::Target foolib( - STATIC_NAME, buildcc::base::TargetType::StaticLibrary, gcc, "data"); + buildcc::BaseTarget foolib(STATIC_NAME, buildcc::TargetType::StaticLibrary, + gcc, "data"); foolib.AddSource("foo/foo.cpp"); foolib.AddIncludeDir("foo"); foolib.AddIncludeDir(""); - buildcc::base::m::TargetExpect_DirChanged(1, &foolib); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::m::TargetExpect_DirChanged(1, &foolib); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); foolib.Build(); + buildcc::m::TargetRunner(foolib); - // * To make sure that SaveFile is newer - sleep(1); - bool saved = flatbuffers::SaveFile(foolib.GetTargetPath().string().c_str(), - std::string{""}, false); + // * To make sure that save_file is newer + buildcc::m::blocking_sleep(1); + bool saved = buildcc::env::save_file( + foolib.GetTargetPath().string().c_str(), std::string{""}, false); CHECK_TRUE(saved); - buildcc::base::Target exe_target( - EXE_NAME, buildcc::base::TargetType::Executable, gcc, "data"); + buildcc::BaseTarget exe_target(EXE_NAME, buildcc::TargetType::Executable, + gcc, "data"); exe_target.AddSource("foo_main.cpp"); exe_target.AddIncludeDir("foo"); exe_target.AddLibDep(foolib); - buildcc::base::m::TargetExpect_PathUpdated(1, &exe_target); - buildcc::internal::m::Expect_command(1, true); + buildcc::m::TargetExpect_PathUpdated(1, &exe_target); + buildcc::env::m::CommandExpect_Execute(1, true); exe_target.Build(); + buildcc::m::TargetRunner(exe_target); } mock().checkExpectations(); } int main(int ac, char **av) { - buildcc::env::init(BUILD_SCRIPT_SOURCE, - BUILD_TARGET_LIB_DEP_INTERMEDIATE_DIR); + buildcc::Project::Init(BUILD_SCRIPT_SOURCE, + BUILD_TARGET_LIB_DEP_INTERMEDIATE_DIR); return CommandLineTestRunner::RunAllTests(ac, av); } diff --git a/buildcc/lib/target/test/target/test_target_link_flags.cpp b/buildcc/lib/target/test/target/test_target_link_flags.cpp deleted file mode 100644 index 66452422..00000000 --- a/buildcc/lib/target/test/target/test_target_link_flags.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "constants.h" - -#include "expect_target.h" -#include "target.h" - -#include "env.h" - -// -#include "internal/fbs_loader.h" - -// Third Party -#include "flatbuffers/util.h" - -// NOTE, Make sure all these includes are AFTER the system and header includes -#include "CppUTest/CommandLineTestRunner.h" -#include "CppUTest/MemoryLeakDetectorNewMacros.h" -#include "CppUTest/TestHarness.h" -#include "CppUTest/Utest.h" -#include "CppUTestExt/MockSupport.h" - -// clang-format off -TEST_GROUP(TargetTestLinkFlagsGroup) -{ - void teardown() { - mock().clear(); - } -}; -// clang-format on - -static const buildcc::base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", - "ld"); -static const fs::path target_source_intermediate_path = - fs::path(BUILD_TARGET_LINK_INTERMEDIATE_DIR) / gcc.GetName(); - -TEST(TargetTestLinkFlagsGroup, Target_AddLinkFlag) { - constexpr const char *const NAME = "AddLinkFlag.exe"; - constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; - - auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; - auto intermediate_path = target_source_intermediate_path / NAME; - - // Delete - fs::remove_all(intermediate_path); - - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, gcc, - "data"); - simple.AddSource(DUMMY_MAIN); - simple.AddLinkFlag("-lm"); - - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - - mock().checkExpectations(); - - // Verify binary - buildcc::internal::FbsLoader loader(NAME, simple.GetTargetIntermediateDir()); - bool loaded = loader.Load(); - CHECK_TRUE(loaded); - - CHECK_EQUAL(loader.GetLoadedLinkFlags().size(), 1); -} - -TEST(TargetTestLinkFlagsGroup, Target_ChangedLinkFlag) { - constexpr const char *const NAME = "ChangedLinkFlag.exe"; - constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; - - auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; - auto intermediate_path = target_source_intermediate_path / NAME; - - // Delete - fs::remove_all(intermediate_path); - - { - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - simple.AddSource(DUMMY_MAIN); - simple.AddLinkFlag("-lm"); - - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - } - { - // * Remove flag - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - simple.AddSource(DUMMY_MAIN); - buildcc::base::m::TargetExpect_FlagChanged(1, &simple); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - } - - { - // * Add flag - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - simple.AddSource(DUMMY_MAIN); - simple.AddLinkFlag("-lm"); - buildcc::base::m::TargetExpect_FlagChanged(1, &simple); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - } - - mock().checkExpectations(); -} - -int main(int ac, char **av) { - buildcc::env::init(BUILD_SCRIPT_SOURCE, BUILD_TARGET_LINK_INTERMEDIATE_DIR); - return CommandLineTestRunner::RunAllTests(ac, av); -} diff --git a/buildcc/lib/target/test/target/test_target_pch.cpp b/buildcc/lib/target/test/target/test_target_pch.cpp new file mode 100644 index 00000000..57385eb5 --- /dev/null +++ b/buildcc/lib/target/test/target/test_target_pch.cpp @@ -0,0 +1,534 @@ +#include + +#include "constants.h" + +#include "expect_command.h" +#include "expect_target.h" +#include "test_target_util.h" + +#include "target/target.h" + +#include "env/env.h" +#include "env/util.h" + +// Third Party + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTest/UtestMacros.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(TargetPchTestGroup) +{ + void teardown() { + mock().checkExpectations(); + mock().clear(); + } +}; +// clang-format on + +static buildcc::Toolchain gcc(buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", + "ar", "ld")); + +TEST(TargetPchTestGroup, Target_AddPch) { + constexpr const char *const NAME = "AddPch.exe"; + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddPch("pch/pch_header_2.h"); +} + +TEST(TargetPchTestGroup, Target_AddPch_Build) { + constexpr const char *const NAME = "AddPch_Build.exe"; + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddPch("pch/pch_header_2.h"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + + // Save file + buildcc::env::save_file(target.GetPchCompilePath().string().c_str(), "", + false); + exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + + mock().checkExpectations(); +} + +TEST(TargetPchTestGroup, Target_AddPch_Rebuild) { + constexpr const char *const NAME = "AddPch_Rebuild.exe"; + + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddPch("pch/pch_header_2.h"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Rebuild: No change + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddPch("pch/pch_header_2.h"); + + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Rebuild: Removed + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + + buildcc::m::TargetExpect_PathRemoved(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Rebuild: Added + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddPch("pch/pch_header_2.h"); + + buildcc::m::TargetExpect_PathAdded(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Rebuild: Updated + { + buildcc::m::blocking_sleep(1); + fs::path filename = + fs::path(BUILD_SCRIPT_SOURCE) / "data" / "pch/pch_header_1.h"; + bool save = buildcc::env::save_file(filename.string().c_str(), "", false); + CHECK_TRUE(save); + + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddPch("pch/pch_header_2.h"); + + buildcc::m::TargetExpect_PathUpdated(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + mock().checkExpectations(); +} + +TEST(TargetPchTestGroup, Target_AddPch_CppRebuild) { + constexpr const char *const NAME = "AddPch_CppRebuild.exe"; + + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddPch("pch/pch_header_2.h"); + target.AddSource("dummy_main.cpp"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Rebuild: No change + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddPch("pch/pch_header_2.h"); + target.AddSource("dummy_main.cpp"); + + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Rebuild: Removed + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddSource("dummy_main.cpp"); + + buildcc::m::TargetExpect_PathRemoved(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Rebuild: Added + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddPch("pch/pch_header_2.h"); + target.AddSource("dummy_main.cpp"); + + buildcc::m::TargetExpect_PathAdded(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Rebuild: Updated + { + buildcc::m::blocking_sleep(1); + fs::path filename = + fs::path(BUILD_SCRIPT_SOURCE) / "data" / "pch/pch_header_1.h"; + bool save = buildcc::env::save_file(filename.string().c_str(), "", false); + CHECK_TRUE(save); + + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddPch("pch/pch_header_2.h"); + target.AddSource("dummy_main.cpp"); + + buildcc::m::TargetExpect_PathUpdated(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + mock().checkExpectations(); +} + +TEST(TargetPchTestGroup, Target_AddPch_IncludeDirsRebuild) { + constexpr const char *const NAME = "AddPch_IncludeDirsRebuild.exe"; + + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddIncludeDir("pch"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // No Change + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddIncludeDir("pch"); + + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Remove + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + // target.AddIncludeDir("pch"); + + buildcc::m::TargetExpect_DirChanged(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Added + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddIncludeDir("pch"); + + buildcc::m::TargetExpect_DirChanged(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } +} + +TEST(TargetPchTestGroup, Target_AddPch_HeadersRebuild) { + constexpr const char *const NAME = "AddPch_HeadersRebuild.exe"; + + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddHeader("pch/pch_header_1.h"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // No Change + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddHeader("pch/pch_header_1.h"); + + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Remove + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + // target.AddHeader("pch/pch_header_1.h"); + + buildcc::m::TargetExpect_PathChanged(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Added + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddHeader("pch/pch_header_1.h"); + + buildcc::m::TargetExpect_PathChanged(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } +} + +TEST(TargetPchTestGroup, Target_AddPchs_FlagsRebuild) { + constexpr const char *const NAME = "Target_AddPchs_FlagsRebuild.exe"; + + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Added Preprocessor flag + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPreprocessorFlag("-H"); + target.AddPch("pch/pch_header_1.h"); + + buildcc::m::TargetExpect_FlagChanged(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Added CommonCompileFlag + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPreprocessorFlag("-H"); + target.AddCommonCompileFlag("-H"); + target.AddPch("pch/pch_header_1.h"); + + buildcc::m::TargetExpect_FlagChanged(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Added PchCompileFlag + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPreprocessorFlag("-H"); + target.AddCommonCompileFlag("-H"); + target.AddPchCompileFlag("-H"); + target.AddPch("pch/pch_header_1.h"); + + buildcc::m::TargetExpect_FlagChanged(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Added CCompileFlag + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPreprocessorFlag("-H"); + target.AddCommonCompileFlag("-H"); + target.AddPchCompileFlag("-H"); + target.AddCCompileFlag("-H"); + target.AddPch("pch/pch_header_1.h"); + + buildcc::m::TargetExpect_FlagChanged(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } + + // Keep CCompileFlag, Added CppCompileFlag + { + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPreprocessorFlag("-H"); + target.AddCommonCompileFlag("-H"); + target.AddPchCompileFlag("-H"); + target.AddCCompileFlag("-H"); + target.AddCppCompileFlag("-H"); + target.AddPch("pch/pch_header_1.h"); + + buildcc::m::TargetExpect_FlagChanged(1, &target); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + } +} + +TEST(TargetPchTestGroup, Target_AddPchObjectFlag_Build) { + constexpr const char *const NAME = "AddPchObjectFlag_Build.exe"; + + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPchObjectFlag("-H"); + target.AddPch("pch/pch_header_1.h"); + target.AddPch("pch/pch_header_2.h"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_TRUE(exists); + CHECK_EQUAL(target.GetPchObjectFlags().size(), 1); + + mock().checkExpectations(); +} + +TEST(TargetPchTestGroup, Target_BadPch) { + constexpr const char *const NAME = "Target_BadPch.exe"; + buildcc::BaseTarget target(NAME, buildcc::TargetType::Executable, gcc, + "data"); + target.AddPch("pch/pch_header_1.h"); + target.AddPch("pch/pch_header_2.h"); + + buildcc::env::set_task_state(buildcc::env::TaskState::FAILURE); + + target.Build(); + buildcc::m::TargetRunner(target); + bool exists = fs::exists(target.GetPchHeaderPath()); + CHECK_FALSE(exists); + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + // Save file + exists = fs::exists(target.GetPchHeaderPath()); + CHECK_FALSE(exists); + + mock().checkExpectations(); +} + +int main(int ac, char **av) { + const fs::path target_source_intermediate_path = + fs::path(BUILD_TARGET_PCH_INTERMEDIATE_DIR) / gcc.GetName(); + fs::remove_all(target_source_intermediate_path); + + buildcc::Project::Init(BUILD_SCRIPT_SOURCE, + BUILD_TARGET_PCH_INTERMEDIATE_DIR); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/target/test/target/test_target_preprocessor_flags.cpp b/buildcc/lib/target/test/target/test_target_preprocessor_flags.cpp deleted file mode 100644 index 5a72a99b..00000000 --- a/buildcc/lib/target/test/target/test_target_preprocessor_flags.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#include "constants.h" - -#include "expect_target.h" -#include "target.h" - -#include "env.h" - -// -#include "internal/fbs_loader.h" - -// Third Party -#include "flatbuffers/util.h" - -// NOTE, Make sure all these includes are AFTER the system and header includes -#include "CppUTest/CommandLineTestRunner.h" -#include "CppUTest/MemoryLeakDetectorNewMacros.h" -#include "CppUTest/TestHarness.h" -#include "CppUTest/Utest.h" -#include "CppUTestExt/MockSupport.h" - -// clang-format off -TEST_GROUP(TargetTestPreprocessorFlagGroup) -{ - void teardown() { - mock().clear(); - } -}; -// clang-format on - -static const buildcc::base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", - "ld"); -static const fs::path target_source_intermediate_path = - fs::path(BUILD_TARGET_PREPROCESSOR_INTERMEDIATE_DIR) / gcc.GetName(); - -TEST(TargetTestPreprocessorFlagGroup, Target_AddPreprocessorFlag) { - constexpr const char *const NAME = "AddPreprocessorFlag.exe"; - constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; - - auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; - auto intermediate_path = target_source_intermediate_path / NAME; - - // Delete - fs::remove_all(intermediate_path); - - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, gcc, - "data"); - simple.AddSource(DUMMY_MAIN); - simple.AddPreprocessorFlag("-DCOMPILE=1"); - - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - - mock().checkExpectations(); - - // Verify binary - buildcc::internal::FbsLoader loader(NAME, simple.GetTargetIntermediateDir()); - bool loaded = loader.Load(); - CHECK_TRUE(loaded); - - CHECK_EQUAL(loader.GetLoadedPreprocessorFlags().size(), 1); -} - -TEST(TargetTestPreprocessorFlagGroup, Target_ChangedPreprocessorFlag) { - constexpr const char *const NAME = "ChangedPreprocessorFlag.exe"; - constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; - - auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; - auto intermediate_path = target_source_intermediate_path / NAME; - - // Delete - fs::remove_all(intermediate_path); - - { - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - simple.AddSource(DUMMY_MAIN); - simple.AddPreprocessorFlag("-DCOMPILE=1"); - - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - } - { - // * Remove flag - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - simple.AddSource(DUMMY_MAIN); - buildcc::base::m::TargetExpect_FlagChanged(1, &simple); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - } - - { - // * Add flag - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - simple.AddSource(DUMMY_MAIN); - simple.AddPreprocessorFlag("-DRANDOM=1"); - buildcc::base::m::TargetExpect_FlagChanged(1, &simple); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); - } - - mock().checkExpectations(); -} - -int main(int ac, char **av) { - buildcc::env::init(BUILD_SCRIPT_SOURCE, - BUILD_TARGET_PREPROCESSOR_INTERMEDIATE_DIR); - return CommandLineTestRunner::RunAllTests(ac, av); -} diff --git a/buildcc/lib/target/test/target/test_target_source.cpp b/buildcc/lib/target/test/target/test_target_source.cpp index 7c84f07e..4e34eb28 100644 --- a/buildcc/lib/target/test/target/test_target_source.cpp +++ b/buildcc/lib/target/test/target/test_target_source.cpp @@ -1,12 +1,15 @@ #include "constants.h" +#include "expect_command.h" #include "expect_target.h" -#include "target.h" +#include "test_target_util.h" -#include "env.h" +#include "target/target.h" + +#include "env/env.h" +#include "env/util.h" // Third Party -#include "flatbuffers/util.h" // NOTE, Make sure all these includes are AFTER the system and header includes #include "CppUTest/CommandLineTestRunner.h" @@ -24,16 +27,18 @@ TEST_GROUP(TargetTestSourceGroup) }; // clang-format on -static const buildcc::base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", - "ld"); +static buildcc::Toolchain gcc(buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", + "ar", "ld")); + static const fs::path target_source_intermediate_path = fs::path(BUILD_TARGET_SOURCE_INTERMEDIATE_DIR) / gcc.GetName(); TEST(TargetTestSourceGroup, Target_SourceTypes) { constexpr const char *const NAME = "SourceTypes.exe"; auto intermediate_path = target_source_intermediate_path / NAME; - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, gcc, - "data"); + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); simple.AddSource("fileext/c_file.c"); simple.AddSource("fileext/cpp_file1.cpp"); @@ -43,7 +48,7 @@ TEST(TargetTestSourceGroup, Target_SourceTypes) { simple.AddSource("fileext/asm_file2.S"); simple.AddSource("fileext/asm_file3.asm"); - CHECK_EQUAL(simple.GetCurrentSourceFiles().size(), 7); + CHECK_EQUAL(simple.GetSourceFiles().size(), 7); CHECK_THROWS(std::exception, simple.AddSource("fileext/header_file1.h")); CHECK_THROWS(std::exception, simple.AddSource("fileext/header_file2.hpp")); CHECK_THROWS(std::exception, @@ -53,7 +58,6 @@ TEST(TargetTestSourceGroup, Target_SourceTypes) { TEST(TargetTestSourceGroup, Target_AddSource) { constexpr const char *const NAME = "AddSource.exe"; constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; - constexpr const char *const NO_FILE = "no_file.cpp"; auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; auto intermediate_path = target_source_intermediate_path / NAME; @@ -61,13 +65,9 @@ TEST(TargetTestSourceGroup, Target_AddSource) { // Delete fs::remove_all(intermediate_path); - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, gcc, - "data"); + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); simple.AddSource(DUMMY_MAIN); - // File does not exist - CHECK_THROWS(std::exception, simple.AddSource(NO_FILE)); - // Duplicate file added - CHECK_THROWS(std::exception, simple.AddSource(DUMMY_MAIN)); } TEST(TargetTestSourceGroup, Target_GlobSource) { @@ -77,10 +77,10 @@ TEST(TargetTestSourceGroup, Target_GlobSource) { // Delete fs::remove_all(intermediate_path); - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, gcc, - "data"); + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); simple.GlobSources(""); - CHECK_EQUAL(simple.GetCurrentSourceFiles().size(), 6); + CHECK_EQUAL(simple.GetSourceFiles().size(), 6); } TEST(TargetTestSourceGroup, Target_Build_SourceCompile) { @@ -93,61 +93,32 @@ TEST(TargetTestSourceGroup, Target_Build_SourceCompile) { // Delete fs::remove_all(intermediate_path); - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, gcc, - "data"); - - buildcc::internal::m::Expect_command(1, true); // compile - buildcc::internal::m::Expect_command(1, true); // link + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); simple.AddSource(DUMMY_MAIN); simple.Build(); + buildcc::env::m::CommandExpect_Execute(1, true); // compile + buildcc::env::m::CommandExpect_Execute(1, true); // link + buildcc::m::TargetRunner(simple); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + mock().checkExpectations(); - buildcc::internal::FbsLoader loader(NAME, intermediate_path); - bool is_loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization(simple.GetBinaryPath()); + bool is_loaded = serialization.LoadFromFile(); CHECK_TRUE(is_loaded); - const auto &loaded_sources = loader.GetLoadedSources(); + const auto &loaded_sources = + serialization.GetLoad().sources.GetUnorderedPathInfos(); CHECK_EQUAL(loaded_sources.size(), 1); - auto dummy_file = buildcc::internal::Path::CreateExistingPath( - (source_path / DUMMY_MAIN).string()); + auto dummy_file = buildcc::internal::PathInfo::ToPathString( + fs::path(source_path / DUMMY_MAIN).string()); CHECK_FALSE(loaded_sources.find(dummy_file) == loaded_sources.end()); } -TEST(TargetTestSourceGroup, Target_Build_SourceCompileError) { - constexpr const char *const NAME = "CompileError.exe"; - constexpr const char *const DUMMY_MAIN = "dummy_main.cpp"; - - auto source_path = fs::path(BUILD_SCRIPT_SOURCE) / "data"; - auto intermediate_path = target_source_intermediate_path / NAME; - - // Delete - - { - fs::remove_all(intermediate_path); - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - - simple.AddSource(DUMMY_MAIN); - buildcc::internal::m::Expect_command(1, false); // compile - CHECK_THROWS(std::exception, simple.Build()); - } - - { - fs::remove_all(intermediate_path); - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); - - simple.AddSource(DUMMY_MAIN); - buildcc::internal::m::Expect_command(1, true); // compile - buildcc::internal::m::Expect_command(1, false); // compile - CHECK_THROWS(std::exception, simple.Build()); - } - - mock().checkExpectations(); -} - TEST(TargetTestSourceGroup, Target_Build_SourceRecompile) { constexpr const char *const NAME = "Recompile.exe"; constexpr const char *const DUMMY_MAIN_CPP = "dummy_main.cpp"; @@ -159,88 +130,99 @@ TEST(TargetTestSourceGroup, Target_Build_SourceRecompile) { // Delete fs::remove_all(intermediate_path); - auto dummy_c_file = - buildcc::internal::Path::CreateExistingPath((source_path / DUMMY_MAIN_C)); - auto dummy_cpp_file = buildcc::internal::Path::CreateExistingPath( - (source_path / DUMMY_MAIN_CPP)); - auto new_source_file = - buildcc::internal::Path::CreateExistingPath((source_path / NEW_SOURCE)); + auto dummy_c_file = buildcc::internal::PathInfo::ToPathString( + (source_path / DUMMY_MAIN_C).string()); + auto dummy_cpp_file = buildcc::internal::PathInfo::ToPathString( + (source_path / DUMMY_MAIN_CPP).string()); + auto new_source_file = buildcc::internal::PathInfo::ToPathString( + (source_path / NEW_SOURCE).string()); { - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); // * Test C compile simple.AddSource(DUMMY_MAIN_C); simple.AddSource(NEW_SOURCE); - buildcc::internal::m::Expect_command(1, true); // compile - buildcc::internal::m::Expect_command(1, true); // compile - buildcc::internal::m::Expect_command(1, true); // link + buildcc::env::m::CommandExpect_Execute(1, true); // compile + buildcc::env::m::CommandExpect_Execute(1, true); // compile + buildcc::env::m::CommandExpect_Execute(1, true); // link simple.Build(); + buildcc::m::TargetRunner(simple); - buildcc::internal::FbsLoader loader(NAME, intermediate_path); - bool is_loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization( + simple.GetBinaryPath()); + bool is_loaded = serialization.LoadFromFile(); CHECK_TRUE(is_loaded); - const auto &loaded_sources = loader.GetLoadedSources(); + const auto &loaded_sources = + serialization.GetLoad().sources.GetUnorderedPathInfos(); CHECK_EQUAL(loaded_sources.size(), 2); CHECK_FALSE(loaded_sources.find(dummy_c_file) == loaded_sources.end()); CHECK_FALSE(loaded_sources.find(new_source_file) == loaded_sources.end()); } { - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); // * Remove C source // * Add CPP source simple.AddSource(DUMMY_MAIN_CPP); simple.AddSource(NEW_SOURCE); - buildcc::base::m::TargetExpect_SourceRemoved(1, &simple); + buildcc::m::TargetExpect_SourceRemoved(1, &simple); // Added and compiled - buildcc::internal::m::Expect_command(1, true); - buildcc::base::m::TargetExpect_SourceAdded(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::TargetExpect_SourceAdded(1, &simple); // Rebuild target - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); // Run the second Build to test Recompile simple.Build(); + buildcc::m::TargetRunner(simple); - buildcc::internal::FbsLoader loader(NAME, intermediate_path); - bool is_loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization( + simple.GetBinaryPath()); + bool is_loaded = serialization.LoadFromFile(); CHECK_TRUE(is_loaded); - const auto &loaded_sources = loader.GetLoadedSources(); + const auto &loaded_sources = + serialization.GetLoad().sources.GetUnorderedPathInfos(); CHECK_EQUAL(loaded_sources.size(), 2); CHECK_FALSE(loaded_sources.find(dummy_cpp_file) == loaded_sources.end()); CHECK_FALSE(loaded_sources.find(new_source_file) == loaded_sources.end()); } { + buildcc::m::blocking_sleep(1); + // * Force copy to trigger recompile for NEW_SOURCE // *2 Current file is updated auto file_path = source_path / NEW_SOURCE; - flatbuffers::SaveFile(file_path.string().c_str(), std::string{""}, false); + buildcc::env::save_file(file_path.string().c_str(), std::string{""}, false); - buildcc::base::Target simple(NAME, buildcc::base::TargetType::Executable, - gcc, "data"); + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); simple.AddSource(DUMMY_MAIN_CPP); simple.AddSource(NEW_SOURCE); // Run the second Build to test Recompile - buildcc::internal::m::Expect_command(1, true); - buildcc::base::m::TargetExpect_SourceUpdated(1, &simple); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::TargetExpect_SourceUpdated(1, &simple); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); simple.Build(); + buildcc::m::TargetRunner(simple); - buildcc::internal::FbsLoader loader(NAME, intermediate_path); - bool is_loaded = loader.Load(); + buildcc::internal::TargetSerialization serialization( + simple.GetBinaryPath()); + bool is_loaded = serialization.LoadFromFile(); CHECK_TRUE(is_loaded); - const auto &loaded_sources = loader.GetLoadedSources(); + const auto &loaded_sources = + serialization.GetLoad().sources.GetUnorderedPathInfos(); CHECK_EQUAL(loaded_sources.size(), 2); CHECK_FALSE(loaded_sources.find(dummy_cpp_file) == loaded_sources.end()); CHECK_FALSE(loaded_sources.find(new_source_file) == loaded_sources.end()); @@ -249,7 +231,29 @@ TEST(TargetTestSourceGroup, Target_Build_SourceRecompile) { mock().checkExpectations(); } +TEST(TargetTestSourceGroup, Target_CompileCommand_Throws) { + constexpr const char *const NAME = "CompileCommand_Throws.exe"; + auto intermediate_path = target_source_intermediate_path / NAME; + + // Delete + fs::remove_all(intermediate_path); + { + buildcc::BaseTarget simple(NAME, buildcc::TargetType::Executable, gcc, + "data"); + simple.AddSource("dummy_main.c"); + + auto p = simple.GetTargetRootDir() / "dummy_main.c"; + p.make_preferred(); + + // Throws when you call CompileCommand before Build + CHECK_THROWS(std::exception, simple.GetCompileCommand(p)); + // Link Command will be empty before Build + STRCMP_EQUAL(simple.GetLinkCommand().c_str(), ""); + } +} + int main(int ac, char **av) { - buildcc::env::init(BUILD_SCRIPT_SOURCE, BUILD_TARGET_SOURCE_INTERMEDIATE_DIR); + buildcc::Project::Init(BUILD_SCRIPT_SOURCE, + BUILD_TARGET_SOURCE_INTERMEDIATE_DIR); return CommandLineTestRunner::RunAllTests(ac, av); } diff --git a/buildcc/lib/target/test/target/test_target_source_out_of_root.cpp b/buildcc/lib/target/test/target/test_target_source_out_of_root.cpp index e0413a1c..5381f659 100644 --- a/buildcc/lib/target/test/target/test_target_source_out_of_root.cpp +++ b/buildcc/lib/target/test/target/test_target_source_out_of_root.cpp @@ -1,13 +1,14 @@ #include "constants.h" +#include "expect_command.h" #include "expect_target.h" -#include "target.h" -#include "env.h" -#include "logging.h" +#include "target/target.h" + +#include "env/env.h" +#include "env/logging.h" // Third Party -#include "flatbuffers/util.h" // NOTE, Make sure all these includes are AFTER the system and header includes #include "CppUTest/CommandLineTestRunner.h" @@ -25,8 +26,10 @@ TEST_GROUP(TargetTestSourceOutOfRootGroup) }; // clang-format on -static const buildcc::base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", - "ld"); +static buildcc::Toolchain gcc(buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", + "ar", "ld")); + static const fs::path target_source_intermediate_path = fs::path(BUILD_TARGET_SOURCE_OUT_OF_ROOT_INTERMEDIATE_DIR) / gcc.GetName(); @@ -35,13 +38,14 @@ TEST(TargetTestSourceOutOfRootGroup, Add_OutOfRootSource) { fs::remove_all(target_source_intermediate_path / OUTOFROOT); - buildcc::base::Target simple(OUTOFROOT, buildcc::base::TargetType::Executable, - gcc, ""); + buildcc::BaseTarget simple(OUTOFROOT, buildcc::TargetType::Executable, gcc, + ""); simple.AddSource("../dummy_main.cpp"); - buildcc::internal::m::Expect_command(1, true); - buildcc::internal::m::Expect_command(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); simple.Build(); + buildcc::m::TargetRunner(simple); } TEST(TargetTestSourceOutOfRootGroup, Glob_OutOfRootSource) { @@ -49,27 +53,42 @@ TEST(TargetTestSourceOutOfRootGroup, Glob_OutOfRootSource) { fs::remove_all(target_source_intermediate_path / OUTOFROOT); - buildcc::base::Target simple(OUTOFROOT, buildcc::base::TargetType::Executable, - gcc, ""); - simple.GlobSources(".."); // 6 files detected - simple.GlobSourcesAbsolute(fs::path(BUILD_SCRIPT_SOURCE) / "data", - target_source_intermediate_path / - simple.GetName() / - "OUT_OF_SOURCE"); // 6 files detected + { + buildcc::BaseTarget simple(OUTOFROOT, buildcc::TargetType::Executable, gcc, + ""); + simple.GlobSources(".."); // 6 files detected + CHECK_EQUAL(6, simple.GetSourceFiles().size()); - CHECK_EQUAL(12, simple.GetCurrentSourceFiles().size()); + buildcc::env::m::CommandExpect_Execute(6, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + } - buildcc::internal::m::Expect_command(12, true); - buildcc::internal::m::Expect_command(1, true); - simple.Build(); + mock().checkExpectations(); +} + +TEST(TargetTestSourceOutOfRootGroup, GlobAbsolute_OutOfRootSource) { + constexpr const char *const OUTOFROOT = "GlobAbsoluteOutOfRootSources.exe"; + fs::remove_all(target_source_intermediate_path / OUTOFROOT); + { + buildcc::BaseTarget simple(OUTOFROOT, buildcc::TargetType::Executable, gcc, + ""); + simple.GlobSourcesAbsolute(fs::path(BUILD_SCRIPT_SOURCE) / + "data"); // 6 files detected + buildcc::env::m::CommandExpect_Execute(6, true); + buildcc::env::m::CommandExpect_Execute(1, true); + simple.Build(); + buildcc::m::TargetRunner(simple); + } mock().checkExpectations(); } int main(int ac, char **av) { std::filesystem::create_directories(fs::path(BUILD_SCRIPT_SOURCE) / "data" / "random dir"); - buildcc::env::init(fs::path(BUILD_SCRIPT_SOURCE) / "data" / "random dir", - BUILD_TARGET_SOURCE_OUT_OF_ROOT_INTERMEDIATE_DIR); + buildcc::Project::Init(fs::path(BUILD_SCRIPT_SOURCE) / "data" / "random dir", + BUILD_TARGET_SOURCE_OUT_OF_ROOT_INTERMEDIATE_DIR); return CommandLineTestRunner::RunAllTests(ac, av); } diff --git a/buildcc/lib/target/test/target/test_target_state.cpp b/buildcc/lib/target/test/target/test_target_state.cpp new file mode 100644 index 00000000..ec8806c9 --- /dev/null +++ b/buildcc/lib/target/test/target/test_target_state.cpp @@ -0,0 +1,38 @@ +#include "target/common/target_state.h" +#include "toolchain/common/file_ext.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" + +// clang-format off +TEST_GROUP(TargetStateTestGroup) +{ +}; +// clang-format on + +TEST(TargetStateTestGroup, SetSourceState) { + buildcc::TargetState target_state; + + CHECK_FALSE(target_state.ContainsC()); + target_state.SourceDetected(buildcc::FileExt::C); + CHECK_TRUE(target_state.ContainsC()); + + CHECK_FALSE(target_state.ContainsCpp()); + target_state.SourceDetected(buildcc::FileExt::Cpp); + CHECK_TRUE(target_state.ContainsCpp()); + + CHECK_FALSE(target_state.ContainsAsm()); + target_state.SourceDetected(buildcc::FileExt::Asm); + CHECK_TRUE(target_state.ContainsAsm()); + + // Ignored + target_state.SourceDetected(buildcc::FileExt::Header); + target_state.SourceDetected(buildcc::FileExt::Invalid); +} + +int main(int ac, char **av) { + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/target/test/target/test_target_sync.cpp b/buildcc/lib/target/test/target/test_target_sync.cpp new file mode 100644 index 00000000..50e7f207 --- /dev/null +++ b/buildcc/lib/target/test/target/test_target_sync.cpp @@ -0,0 +1,325 @@ +#include "constants.h" + +#include "target/target.h" + +#include "env/env.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(TargetTestSyncGroup) +{ + void teardown() { + } +}; +// clang-format on + +static buildcc::Toolchain gcc(buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", + "ar", "ld")); + +TEST(TargetTestSyncGroup, CopyByConstRef) { + buildcc::BaseTarget srcTarget("srcTarget", buildcc::TargetType::Executable, + gcc, "data"); + buildcc::BaseTarget destTarget("destTarget", buildcc::TargetType::Executable, + gcc, "data"); + + srcTarget.AddSource("dummy_main.c"); + srcTarget.AddIncludeDir("include", true); + srcTarget.AddPch("include/include_header.h"); + srcTarget.AddLibDep(srcTarget); + srcTarget.AddLibDep("testLib.a"); + srcTarget.AddLibDir("include"); + + srcTarget.AddPreprocessorFlag("-DTEST"); + srcTarget.AddCommonCompileFlag("-O0"); + srcTarget.AddPchCompileFlag("-pch_compile"); + srcTarget.AddPchObjectFlag("-pch_object"); + srcTarget.AddAsmCompileFlag("-march=test"); + srcTarget.AddCCompileFlag("-std=c11"); + srcTarget.AddCppCompileFlag("-std=c++17"); + srcTarget.AddLinkFlag("-nostdinc"); + srcTarget.AddCompileDependency("new_source.cpp"); + srcTarget.AddLinkDependency("new_source.cpp"); + + destTarget.Copy(srcTarget, { + buildcc::SyncOption::SourceFiles, + buildcc::SyncOption::HeaderFiles, + buildcc::SyncOption::PchFiles, + buildcc::SyncOption::LibDeps, + buildcc::SyncOption::IncludeDirs, + buildcc::SyncOption::LibDirs, + buildcc::SyncOption::ExternalLibDeps, + buildcc::SyncOption::PreprocessorFlags, + buildcc::SyncOption::CommonCompileFlags, + buildcc::SyncOption::PchCompileFlags, + buildcc::SyncOption::PchObjectFlags, + buildcc::SyncOption::AsmCompileFlags, + buildcc::SyncOption::CCompileFlags, + buildcc::SyncOption::CppCompileFlags, + buildcc::SyncOption::LinkFlags, + buildcc::SyncOption::CompileDependencies, + buildcc::SyncOption::LinkDependencies, + }); + + CHECK_EQUAL(destTarget.GetSourceFiles().size(), 1); + CHECK_EQUAL(destTarget.GetHeaderFiles().size(), 1); + CHECK_EQUAL(destTarget.GetPchFiles().size(), 1); + + CHECK_EQUAL(destTarget.GetLibDeps().size(), 1); + CHECK_EQUAL(destTarget.GetExternalLibDeps().size(), 1); + + CHECK_EQUAL(destTarget.GetIncludeDirs().size(), 1); + CHECK_EQUAL(destTarget.GetLibDirs().size(), 1); + + CHECK_EQUAL(destTarget.GetPreprocessorFlags().size(), 1); + CHECK_EQUAL(destTarget.GetCommonCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetPchCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetPchObjectFlags().size(), 1); + CHECK_EQUAL(destTarget.GetAsmCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetCCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetCppCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetLinkFlags().size(), 1); + + CHECK_EQUAL(destTarget.GetCompileDependencies().size(), 1); + CHECK_EQUAL(destTarget.GetLinkDependencies().size(), 1); +} + +TEST(TargetTestSyncGroup, CopyByMove) { + buildcc::BaseTarget srcTarget("srcTarget", buildcc::TargetType::Executable, + gcc, "data"); + buildcc::BaseTarget destTarget("destTarget", buildcc::TargetType::Executable, + gcc, "data"); + + srcTarget.AddSource("dummy_main.c"); + srcTarget.AddIncludeDir("include", true); + srcTarget.AddPch("include/include_header.h"); + srcTarget.AddLibDep(srcTarget); + srcTarget.AddLibDep("testLib.a"); + srcTarget.AddLibDir("include"); + + srcTarget.AddPreprocessorFlag("-DTEST"); + srcTarget.AddCommonCompileFlag("-O0"); + srcTarget.AddPchCompileFlag("-pch_compile"); + srcTarget.AddPchObjectFlag("-pch_object"); + srcTarget.AddAsmCompileFlag("-march=test"); + srcTarget.AddCCompileFlag("-std=c11"); + srcTarget.AddCppCompileFlag("-std=c++17"); + srcTarget.AddLinkFlag("-nostdinc"); + srcTarget.AddCompileDependency("new_source.cpp"); + srcTarget.AddLinkDependency("new_source.cpp"); + + destTarget.Copy(std::move(srcTarget), + { + buildcc::SyncOption::SourceFiles, + buildcc::SyncOption::HeaderFiles, + buildcc::SyncOption::PchFiles, + buildcc::SyncOption::LibDeps, + buildcc::SyncOption::IncludeDirs, + buildcc::SyncOption::LibDirs, + buildcc::SyncOption::ExternalLibDeps, + buildcc::SyncOption::PreprocessorFlags, + buildcc::SyncOption::CommonCompileFlags, + buildcc::SyncOption::PchCompileFlags, + buildcc::SyncOption::PchObjectFlags, + buildcc::SyncOption::AsmCompileFlags, + buildcc::SyncOption::CCompileFlags, + buildcc::SyncOption::CppCompileFlags, + buildcc::SyncOption::LinkFlags, + buildcc::SyncOption::CompileDependencies, + buildcc::SyncOption::LinkDependencies, + }); + + CHECK_EQUAL(destTarget.GetSourceFiles().size(), 1); + CHECK_EQUAL(destTarget.GetHeaderFiles().size(), 1); + CHECK_EQUAL(destTarget.GetPchFiles().size(), 1); + + CHECK_EQUAL(destTarget.GetLibDeps().size(), 1); + CHECK_EQUAL(destTarget.GetExternalLibDeps().size(), 1); + + CHECK_EQUAL(destTarget.GetIncludeDirs().size(), 1); + CHECK_EQUAL(destTarget.GetLibDirs().size(), 1); + + CHECK_EQUAL(destTarget.GetPreprocessorFlags().size(), 1); + CHECK_EQUAL(destTarget.GetCommonCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetPchCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetPchObjectFlags().size(), 1); + CHECK_EQUAL(destTarget.GetAsmCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetCCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetCppCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetLinkFlags().size(), 1); + + CHECK_EQUAL(destTarget.GetCompileDependencies().size(), 1); + CHECK_EQUAL(destTarget.GetLinkDependencies().size(), 1); +} + +TEST(TargetTestSyncGroup, CopyCrash) { + buildcc::BaseTarget srcTarget("srcTarget", buildcc::TargetType::Executable, + gcc, "data"); + buildcc::BaseTarget destTarget("destTarget", buildcc::TargetType::Executable, + gcc, "data"); + + CHECK_THROWS(std::exception, + destTarget.Copy(srcTarget, { + (buildcc::SyncOption)65535, + })); +} + +TEST(TargetTestSyncGroup, InsertByConstRef) { + buildcc::BaseTarget srcTarget("srcTarget", buildcc::TargetType::Executable, + gcc, "data"); + buildcc::BaseTarget destTarget("destTarget", buildcc::TargetType::Executable, + gcc, "data"); + + srcTarget.AddSource("dummy_main.c"); + srcTarget.AddIncludeDir("include", true); + srcTarget.AddPch("include/include_header.h"); + srcTarget.AddLibDep(srcTarget); + srcTarget.AddLibDep("testLib.a"); + srcTarget.AddLibDir("include"); + + srcTarget.AddPreprocessorFlag("-DTEST"); + srcTarget.AddCommonCompileFlag("-O0"); + srcTarget.AddPchCompileFlag("-pch_compile"); + srcTarget.AddPchObjectFlag("-pch_object"); + srcTarget.AddAsmCompileFlag("-march=test"); + srcTarget.AddCCompileFlag("-std=c11"); + srcTarget.AddCppCompileFlag("-std=c++17"); + srcTarget.AddLinkFlag("-nostdinc"); + srcTarget.AddCompileDependency("new_source.cpp"); + srcTarget.AddLinkDependency("new_source.cpp"); + + destTarget.Insert(srcTarget, { + buildcc::SyncOption::SourceFiles, + buildcc::SyncOption::HeaderFiles, + buildcc::SyncOption::PchFiles, + buildcc::SyncOption::LibDeps, + buildcc::SyncOption::IncludeDirs, + buildcc::SyncOption::LibDirs, + buildcc::SyncOption::ExternalLibDeps, + buildcc::SyncOption::PreprocessorFlags, + buildcc::SyncOption::CommonCompileFlags, + buildcc::SyncOption::PchCompileFlags, + buildcc::SyncOption::PchObjectFlags, + buildcc::SyncOption::AsmCompileFlags, + buildcc::SyncOption::CCompileFlags, + buildcc::SyncOption::CppCompileFlags, + buildcc::SyncOption::LinkFlags, + buildcc::SyncOption::CompileDependencies, + buildcc::SyncOption::LinkDependencies, + }); + + CHECK_EQUAL(destTarget.GetSourceFiles().size(), 1); + CHECK_EQUAL(destTarget.GetHeaderFiles().size(), 1); + CHECK_EQUAL(destTarget.GetPchFiles().size(), 1); + + CHECK_EQUAL(destTarget.GetLibDeps().size(), 1); + CHECK_EQUAL(destTarget.GetExternalLibDeps().size(), 1); + + CHECK_EQUAL(destTarget.GetIncludeDirs().size(), 1); + CHECK_EQUAL(destTarget.GetLibDirs().size(), 1); + + CHECK_EQUAL(destTarget.GetPreprocessorFlags().size(), 1); + CHECK_EQUAL(destTarget.GetCommonCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetPchCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetPchObjectFlags().size(), 1); + CHECK_EQUAL(destTarget.GetAsmCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetCCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetCppCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetLinkFlags().size(), 1); + + CHECK_EQUAL(destTarget.GetCompileDependencies().size(), 1); + CHECK_EQUAL(destTarget.GetLinkDependencies().size(), 1); +} + +TEST(TargetTestSyncGroup, InsertByMove) { + buildcc::BaseTarget srcTarget("srcTarget", buildcc::TargetType::Executable, + gcc, "data"); + buildcc::BaseTarget destTarget("destTarget", buildcc::TargetType::Executable, + gcc, "data"); + + srcTarget.AddSource("dummy_main.c"); + srcTarget.AddIncludeDir("include", true); + srcTarget.AddPch("include/include_header.h"); + srcTarget.AddLibDep(srcTarget); + srcTarget.AddLibDep("testLib.a"); + srcTarget.AddLibDir("include"); + + srcTarget.AddPreprocessorFlag("-DTEST"); + srcTarget.AddCommonCompileFlag("-O0"); + srcTarget.AddPchCompileFlag("-pch_compile"); + srcTarget.AddPchObjectFlag("-pch_object"); + srcTarget.AddAsmCompileFlag("-march=test"); + srcTarget.AddCCompileFlag("-std=c11"); + srcTarget.AddCppCompileFlag("-std=c++17"); + srcTarget.AddLinkFlag("-nostdinc"); + srcTarget.AddCompileDependency("new_source.cpp"); + srcTarget.AddLinkDependency("new_source.cpp"); + + destTarget.Insert(std::move(srcTarget), + { + buildcc::SyncOption::SourceFiles, + buildcc::SyncOption::HeaderFiles, + buildcc::SyncOption::PchFiles, + buildcc::SyncOption::LibDeps, + buildcc::SyncOption::IncludeDirs, + buildcc::SyncOption::LibDirs, + buildcc::SyncOption::ExternalLibDeps, + buildcc::SyncOption::PreprocessorFlags, + buildcc::SyncOption::CommonCompileFlags, + buildcc::SyncOption::PchCompileFlags, + buildcc::SyncOption::PchObjectFlags, + buildcc::SyncOption::AsmCompileFlags, + buildcc::SyncOption::CCompileFlags, + buildcc::SyncOption::CppCompileFlags, + buildcc::SyncOption::LinkFlags, + buildcc::SyncOption::CompileDependencies, + buildcc::SyncOption::LinkDependencies, + }); + + CHECK_EQUAL(destTarget.GetSourceFiles().size(), 1); + CHECK_EQUAL(destTarget.GetHeaderFiles().size(), 1); + CHECK_EQUAL(destTarget.GetPchFiles().size(), 1); + + CHECK_EQUAL(destTarget.GetLibDeps().size(), 1); + CHECK_EQUAL(destTarget.GetExternalLibDeps().size(), 1); + + CHECK_EQUAL(destTarget.GetIncludeDirs().size(), 1); + CHECK_EQUAL(destTarget.GetLibDirs().size(), 1); + + CHECK_EQUAL(destTarget.GetPreprocessorFlags().size(), 1); + CHECK_EQUAL(destTarget.GetCommonCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetPchCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetPchObjectFlags().size(), 1); + CHECK_EQUAL(destTarget.GetAsmCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetCCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetCppCompileFlags().size(), 1); + CHECK_EQUAL(destTarget.GetLinkFlags().size(), 1); + + CHECK_EQUAL(destTarget.GetCompileDependencies().size(), 1); + CHECK_EQUAL(destTarget.GetLinkDependencies().size(), 1); +} + +TEST(TargetTestSyncGroup, InsertCrash) { + buildcc::BaseTarget srcTarget("srcTarget", buildcc::TargetType::Executable, + gcc, "data"); + buildcc::BaseTarget destTarget("destTarget", buildcc::TargetType::Executable, + gcc, "data"); + + CHECK_THROWS(std::exception, + destTarget.Insert(srcTarget, { + (buildcc::SyncOption)65535, + })); +} + +int main(int ac, char **av) { + buildcc::Project::Init(BUILD_SCRIPT_SOURCE, + BUILD_TARGET_SYNC_INTERMEDIATE_DIR); + fs::remove_all(buildcc::Project::GetBuildDir()); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/target/test/target/test_target_user_deps.cpp b/buildcc/lib/target/test/target/test_target_user_deps.cpp new file mode 100644 index 00000000..686ca0d9 --- /dev/null +++ b/buildcc/lib/target/test/target/test_target_user_deps.cpp @@ -0,0 +1,149 @@ +#include "constants.h" + +#include "expect_command.h" +#include "expect_target.h" +#include "test_target_util.h" + +#include "target/target.h" + +#include "env/env.h" +#include "env/util.h" + +// Third Party + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(TargetTestUserDepsGroup) +{ + void teardown() { + mock().clear(); + } +}; +// clang-format on + +static buildcc::Toolchain gcc(buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", + "ar", "ld")); + +static const fs::path target_source_intermediate_path = + fs::path(BUILD_TARGET_USER_DEPS_INTERMEDIATE_DIR) / gcc.GetName(); + +TEST(TargetTestUserDepsGroup, Target_Build_CompileDeps_NoChange) { + constexpr const char *NAME = "compileDep_NoChange.exe"; + buildcc::BaseTarget compileDep(NAME, buildcc::TargetType::Executable, gcc, + "data"); + compileDep.AddSource("dummy_main.cpp"); + compileDep.AddCompileDependency("new_source.cpp"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + compileDep.Build(); + buildcc::m::TargetRunner(compileDep); + + mock().checkExpectations(); +} + +TEST(TargetTestUserDepsGroup, Target_Build_LinkDeps_NoChange) { + constexpr const char *NAME = "linkDep_NoChange.exe"; + buildcc::BaseTarget linkDep(NAME, buildcc::TargetType::Executable, gcc, + "data"); + linkDep.AddSource("dummy_main.cpp"); + linkDep.AddLinkDependency("new_source.cpp"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + linkDep.Build(); + buildcc::m::TargetRunner(linkDep); + + mock().checkExpectations(); +} + +TEST(TargetTestUserDepsGroup, Target_Build_CompileDeps_Rebuild) { + constexpr const char *NAME = "compileDep_Rebuild.exe"; + { + buildcc::BaseTarget compileDep(NAME, buildcc::TargetType::Executable, gcc, + "data"); + compileDep.AddSource("dummy_main.cpp"); + compileDep.AddCompileDependency("new_source.cpp"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + compileDep.Build(); + buildcc::m::TargetRunner(compileDep); + } + + { + // * To make sure that save_file is newer + buildcc::m::blocking_sleep(1); + const fs::path new_source = + buildcc::Project::GetRootDir() / "data" / "new_source.cpp"; + std::string buf{""}; + buildcc::env::save_file(new_source.string().c_str(), buf, false); + } + + { + buildcc::BaseTarget compileDep(NAME, buildcc::TargetType::Executable, gcc, + "data"); + compileDep.AddSource("dummy_main.cpp"); + compileDep.AddCompileDependency("new_source.cpp"); + + buildcc::m::TargetExpect_PathUpdated(1, &compileDep); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + compileDep.Build(); + buildcc::m::TargetRunner(compileDep); + } + + mock().checkExpectations(); +} + +TEST(TargetTestUserDepsGroup, Target_Build_LinkDeps_Rebuild) { + constexpr const char *NAME = "linkDep_Rebuild.exe"; + { + buildcc::BaseTarget linkDep(NAME, buildcc::TargetType::Executable, gcc, + "data"); + linkDep.AddSource("dummy_main.cpp"); + linkDep.AddLinkDependency("new_source.cpp"); + + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::env::m::CommandExpect_Execute(1, true); + linkDep.Build(); + buildcc::m::TargetRunner(linkDep); + } + + { + // * To make sure that save_file is newer + buildcc::m::blocking_sleep(1); + const fs::path new_source = + buildcc::Project::GetRootDir() / "data" / "new_source.cpp"; + std::string buf{""}; + buildcc::env::save_file(new_source.string().c_str(), buf, false); + } + + { + buildcc::BaseTarget linkDep(NAME, buildcc::TargetType::Executable, gcc, + "data"); + linkDep.AddSource("dummy_main.cpp"); + linkDep.AddLinkDependency("new_source.cpp"); + + buildcc::m::TargetExpect_PathUpdated(1, &linkDep); // Only link + buildcc::env::m::CommandExpect_Execute(1, true); + linkDep.Build(); + buildcc::m::TargetRunner(linkDep); + } + + mock().checkExpectations(); +} + +int main(int ac, char **av) { + fs::remove_all(target_source_intermediate_path); + buildcc::Project::Init(BUILD_SCRIPT_SOURCE, + BUILD_TARGET_USER_DEPS_INTERMEDIATE_DIR); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/target/test/target/test_template_generator.cpp b/buildcc/lib/target/test/target/test_template_generator.cpp new file mode 100644 index 00000000..451c76ae --- /dev/null +++ b/buildcc/lib/target/test/target/test_template_generator.cpp @@ -0,0 +1,97 @@ +#include "target/template_generator.h" + +#include "expect_command.h" +#include "expect_custom_generator.h" +#include "test_target_util.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(TemplateGeneratorTestGroup) +{ + void teardown() { + mock().checkExpectations(); + mock().clear(); + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + } +}; +// clang-format on + +fs::path BUILD_DIR = fs::current_path() / "intermediate" / "template_generator"; + +TEST(TemplateGeneratorTestGroup, Basic) { + buildcc::TemplateGenerator generator("basic", ""); + generator.Build(); + + buildcc::m::CustomGeneratorRunner(generator); +} + +TEST(TemplateGeneratorTestGroup, Basic_Parse) { + buildcc::TemplateGenerator generator("basic_parse", ""); + generator.AddPatterns({ + {"hello", "Hello"}, + {"world", "World"}, + }); + std::string parsed = generator.Parse("{hello} {world}"); + STRCMP_EQUAL(parsed.c_str(), "Hello World"); +} + +TEST(TemplateGeneratorTestGroup, Basic_InputParse) { + buildcc::TemplateGenerator generator("basic_inputparse", ""); + generator.AddPatterns({ + {"hello", "Hello"}, + {"world", "World"}, + }); + generator.AddTemplate("{current_root_dir}/template/default_values.txt.in", + "{current_build_dir}/default_values.txt"); + generator.AddTemplate("{current_root_dir}/template/hello_world.txt.in", + "{current_build_dir}/hello_world.txt"); + generator.Build(); + + buildcc::m::CustomGeneratorRunner(generator); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); +} + +TEST(TemplateGeneratorTestGroup, Basic_SaveFailure) { + constexpr const char *const NAME = "basic_save_failure"; + { + buildcc::TemplateGenerator generator(NAME, ""); + + fs::create_directories(generator.GetBuildDir() / "default_values.txt"); + + generator.AddTemplate("{current_root_dir}/template/default_values.txt.in", + "{current_build_dir}/default_values.txt"); + generator.Build(); + + buildcc::m::CustomGeneratorRunner(generator); + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + } +} + +TEST(TemplateGeneratorTestGroup, Basic_LoadFailure) { + constexpr const char *const NAME = "basic_load_failure"; + { + buildcc::TemplateGenerator generator(NAME, ""); + + fs::create_directories(generator.GetBuildDir() / "default_values.txt.in"); + + generator.AddTemplate("{current_build_dir}/default_values.txt.in", + "{current_build_dir}/default_values.txt"); + generator.Build(); + + buildcc::m::CustomGeneratorRunner(generator); + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + } +} + +int main(int ac, char **av) { + fs::remove_all(BUILD_DIR); + buildcc::Project::Init(fs::current_path() / "data", BUILD_DIR); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/target/test/target/test_toolchain_flag_api.cpp b/buildcc/lib/target/test/target/test_toolchain_flag_api.cpp new file mode 100644 index 00000000..e48becfb --- /dev/null +++ b/buildcc/lib/target/test/target/test_toolchain_flag_api.cpp @@ -0,0 +1,51 @@ +#include "toolchain/toolchain.h" + +#include "target/target_info.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" + +// clang-format off +TEST_GROUP(ToolchainFlagApiTestGroup) +{ +}; +// clang-format on + +TEST(ToolchainFlagApiTestGroup, BasicTargetTest) { + buildcc::Toolchain toolchain( + buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", "ar", "ld")); + + toolchain.AddPreprocessorFlag("-preprocessor"); + toolchain.AddAsmCompileFlag("-asm"); + toolchain.AddPchCompileFlag("-pchcompile"); + toolchain.AddPchObjectFlag("-pchobject"); + toolchain.AddCommonCompileFlag("-common"); + toolchain.AddCCompileFlag("-c"); + toolchain.AddCppCompileFlag("-cpp"); + toolchain.AddLinkFlag("-link"); + + // TODO, Add this in later + // * We should lock our toolchain before using it + // { CHECK_THROWS(std::exception, (buildcc::TargetInfo(toolchain, ""))); } + + { + buildcc::TargetInfo targetinfo(toolchain, ""); + CHECK_EQUAL(targetinfo.GetPreprocessorFlags().size(), 1); + CHECK_EQUAL(targetinfo.GetAsmCompileFlags().size(), 1); + CHECK_EQUAL(targetinfo.GetPchCompileFlags().size(), 1); + CHECK_EQUAL(targetinfo.GetPchObjectFlags().size(), 1); + CHECK_EQUAL(targetinfo.GetCommonCompileFlags().size(), 1); + CHECK_EQUAL(targetinfo.GetCCompileFlags().size(), 1); + CHECK_EQUAL(targetinfo.GetCppCompileFlags().size(), 1); + CHECK_EQUAL(targetinfo.GetLinkFlags().size(), 1); + } +} + +int main(int ac, char **av) { + MemoryLeakWarningPlugin::turnOffNewDeleteOverloads(); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/toolchain/CMakeLists.txt b/buildcc/lib/toolchain/CMakeLists.txt index c7a12a6e..ff1def9e 100644 --- a/buildcc/lib/toolchain/CMakeLists.txt +++ b/buildcc/lib/toolchain/CMakeLists.txt @@ -1,21 +1,103 @@ -# Toolchain lib -m_clangtidy("toolchain") -add_library(toolchain - toolchain.cpp - toolchain.h -) -target_include_directories(toolchain PUBLIC - $ - $ +set(TOOLCHAIN_SRCS + # COMMON + src/common/toolchain_config.cpp + include/toolchain/common/toolchain_config.h + include/toolchain/common/file_ext.h + + # API + src/api/toolchain_find.cpp + src/api/toolchain_verify.cpp + include/toolchain/api/toolchain_find.h + include/toolchain/api/toolchain_verify.h + include/toolchain/api/flag_api.h + + src/toolchain/toolchain.cpp + include/toolchain/toolchain.h ) -target_compile_options(toolchain PRIVATE ${BUILD_COMPILE_FLAGS}) -target_link_options(toolchain PRIVATE ${BUILD_LINK_FLAGS}) +if (${TESTING}) + add_library(mock_toolchain + ${TOOLCHAIN_SRCS} + ) + target_include_directories(mock_toolchain PUBLIC + include + ) + target_compile_options(mock_toolchain PUBLIC ${TEST_COMPILE_FLAGS} ${BUILD_COMPILE_FLAGS}) + target_link_options(mock_toolchain PUBLIC ${TEST_LINK_FLAGS} ${BUILD_LINK_FLAGS}) + target_link_libraries(mock_toolchain PUBLIC + mock_schema + + CppUTest + CppUTestExt + ${TEST_LINK_LIBS} + ) + + add_executable(test_toolchain_id + test/test_toolchain_id.cpp + ) + target_link_libraries(test_toolchain_id PRIVATE + mock_toolchain + ) + + add_executable(test_toolchain_config + test/test_toolchain_config.cpp + ) + target_link_libraries(test_toolchain_config PRIVATE + mock_toolchain + ) + + add_executable(test_toolchain_find + test/test_toolchain_find.cpp + ) + target_link_libraries(test_toolchain_find PRIVATE + mock_toolchain + ) -# TODO, Add Mocks here if required + add_executable(test_toolchain_verify + test/test_toolchain_verify.cpp + ) + target_link_libraries(test_toolchain_verify PRIVATE + mock_toolchain + ) + + add_test(NAME test_toolchain_id COMMAND test_toolchain_id) + add_test(NAME test_toolchain_config COMMAND test_toolchain_config) + add_test(NAME test_toolchain_find COMMAND test_toolchain_find + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test + ) + add_test(NAME test_toolchain_verify COMMAND test_toolchain_verify + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test + ) +endif() + +if(${BUILDCC_BUILD_AS_SINGLE_LIB}) + target_sources(buildcc PRIVATE + ${TOOLCHAIN_SRCS} + ) + target_include_directories(buildcc PUBLIC + $ + $ + ) +endif() + +if(${BUILDCC_BUILD_AS_INTERFACE}) + m_clangtidy("toolchain") + add_library(toolchain + ${TOOLCHAIN_SRCS} + ) + target_include_directories(toolchain PUBLIC + $ + $ + ) + target_link_libraries(toolchain PUBLIC + schema + ) +endif() -# Toolchain install if (${BUILDCC_INSTALL}) - install(TARGETS toolchain DESTINATION lib EXPORT toolchainConfig) - install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/toolchain.h DESTINATION include/buildcc) - install(EXPORT toolchainConfig DESTINATION lib/cmake/toolchain) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}") + if (${BUILDCC_BUILD_AS_INTERFACE}) + # Toolchain install + install(TARGETS toolchain DESTINATION lib EXPORT toolchainConfig) + install(EXPORT toolchainConfig DESTINATION lib/cmake/toolchain) + endif() endif() diff --git a/buildcc/lib/toolchain/include/toolchain/api/flag_api.h b/buildcc/lib/toolchain/include/toolchain/api/flag_api.h new file mode 100644 index 00000000..7d79388f --- /dev/null +++ b/buildcc/lib/toolchain/include/toolchain/api/flag_api.h @@ -0,0 +1,113 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAIN_API_FLAG_API_H_ +#define TOOLCHAIN_API_FLAG_API_H_ + +#include +#include + +namespace buildcc::internal { + +// Requires +// TargetSchema +template class FlagApi { +public: + void AddPreprocessorFlag(const std::string &flag) { + auto &t = static_cast(*this); + t.user_.preprocessor_flags.push_back(flag); + } + + void AddCommonCompileFlag(const std::string &flag) { + auto &t = static_cast(*this); + t.user_.common_compile_flags.push_back(flag); + } + + void AddPchCompileFlag(const std::string &flag) { + auto &t = static_cast(*this); + t.user_.pch_compile_flags.push_back(flag); + } + + void AddPchObjectFlag(const std::string &flag) { + auto &t = static_cast(*this); + t.user_.pch_object_flags.push_back(flag); + } + + void AddAsmCompileFlag(const std::string &flag) { + auto &t = static_cast(*this); + t.user_.asm_compile_flags.push_back(flag); + } + + void AddCCompileFlag(const std::string &flag) { + auto &t = static_cast(*this); + t.user_.c_compile_flags.push_back(flag); + } + + void AddCppCompileFlag(const std::string &flag) { + auto &t = static_cast(*this); + t.user_.cpp_compile_flags.push_back(flag); + } + + void AddLinkFlag(const std::string &flag) { + auto &t = static_cast(*this); + t.user_.link_flags.push_back(flag); + } + + // Getters + const std::vector &GetPreprocessorFlags() const { + const auto &t = static_cast(*this); + return t.user_.preprocessor_flags; + } + + const std::vector &GetCommonCompileFlags() const { + const auto &t = static_cast(*this); + return t.user_.common_compile_flags; + } + + const std::vector &GetPchCompileFlags() const { + const auto &t = static_cast(*this); + return t.user_.pch_compile_flags; + } + + const std::vector &GetPchObjectFlags() const { + const auto &t = static_cast(*this); + return t.user_.pch_object_flags; + } + + const std::vector &GetAsmCompileFlags() const { + const auto &t = static_cast(*this); + return t.user_.asm_compile_flags; + } + + const std::vector &GetCCompileFlags() const { + const auto &t = static_cast(*this); + return t.user_.c_compile_flags; + } + + const std::vector &GetCppCompileFlags() const { + const auto &t = static_cast(*this); + return t.user_.cpp_compile_flags; + } + + const std::vector &GetLinkFlags() const { + const auto &t = static_cast(*this); + return t.user_.link_flags; + } +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/lib/toolchain/include/toolchain/api/toolchain_find.h b/buildcc/lib/toolchain/include/toolchain/api/toolchain_find.h new file mode 100644 index 00000000..d71f82c5 --- /dev/null +++ b/buildcc/lib/toolchain/include/toolchain/api/toolchain_find.h @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAIN_TOOLCHAIN_FIND_H_ +#define TOOLCHAIN_TOOLCHAIN_FIND_H_ + +#include +#include +#include +#include + +#include "schema/path.h" + +namespace fs = std::filesystem; + +namespace buildcc { + +/** + * @brief Configure the behaviour of Toolchain::Find API. By default searches + * the directories mentioned in the ENV{PATH} variable to find the toolchain. + * @param absolute_search_paths absolute_search_paths expect directories that + * are iterated for exact toolchain matches + * @param env_vars env_vars contain paths that are seperated by OS delimiter. + * These are converted to paths and searched similarly to absolute_search_paths + *
+ * NOTE: env_vars must contain single absolute paths or multiple absolute + * paths seperated by OS delimiter
+ * Example: [Windows] "absolute_path_1;absolute_path_2;..."
+ * Example: [Linux] "absolute_path_1:absolute_path_2:..."
+ */ +struct ToolchainFindConfig { + ToolchainFindConfig(const std::vector &env_vars = {"PATH"}, + const std::vector &absolute_search_paths = {}) + : env_vars(env_vars), absolute_search_paths(absolute_search_paths) {} + + std::vector env_vars; + std::vector absolute_search_paths; +}; + +template class ToolchainFind { +public: + std::vector + Find(const ToolchainFindConfig &config = ToolchainFindConfig()) const; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/toolchain/include/toolchain/api/toolchain_verify.h b/buildcc/lib/toolchain/include/toolchain/api/toolchain_verify.h new file mode 100644 index 00000000..389b54d1 --- /dev/null +++ b/buildcc/lib/toolchain/include/toolchain/api/toolchain_verify.h @@ -0,0 +1,103 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAIN_TOOLCHAIN_VERIFY_H_ +#define TOOLCHAIN_TOOLCHAIN_VERIFY_H_ + +#include +#include + +#include "fmt/format.h" + +#include "env/logging.h" +#include "env/optional.h" + +#include "toolchain/common/toolchain_executables.h" +#include "toolchain/common/toolchain_id.h" + +#include "toolchain/api/toolchain_find.h" + +namespace fs = std::filesystem; + +namespace buildcc { + +/** + * @brief Verified Toolchain information + * @param path Absolute host path where ALL the toolchain executables are found + *
+ * NOTE: All the Toolchain executables must be found in a single folder.
+ * @param compiler_version Compiler version of the verified toolchain + * @param target_arch Target architecture of the verified toolchain + */ +struct ToolchainCompilerInfo { + std::string ToString() const { return fmt::format("{}", *this); } + + fs::path path; + std::string compiler_version; + std::string target_arch; +}; + +// clang-format off +using ToolchainInfoCb = std::function(const ToolchainExecutables &)>; +// clang-format on + +template class ToolchainVerify { +public: + ToolchainVerify() = default; + + /** + * @brief Verify your toolchain executables by searching your operating system + * paths + * Only add the verified path IF all toolchain executables are matched + * + * @param config Search paths to find toolchains + * @return std::vector Operating system can contain + * multiple toolchains of similar names with different versions. Collect all + * of them + */ + ToolchainCompilerInfo + Verify(const ToolchainFindConfig &config = ToolchainFindConfig()); + + /** + * @brief Set ToolchainInfo callback for run time objects + */ + void SetToolchainInfoCb(const ToolchainInfoCb &cb); + const ToolchainInfoCb &GetToolchainInfoCb() const; + +private: + ToolchainInfoCb info_cb_; +}; + +} // namespace buildcc + +constexpr const char *const kVerifiedToolchainFormat = R"({{ + "path": "{}", + "compiler_version": "{}", + "target_arch": "{}" +}})"; + +template <> +struct fmt::formatter : formatter { + template + auto format(const buildcc::ToolchainCompilerInfo &vt, FormatContext &ctx) { + std::string verified_toolchain_info = + fmt::format(kVerifiedToolchainFormat, vt.path.string(), + vt.compiler_version, vt.target_arch); + return formatter::format(verified_toolchain_info, ctx); + } +}; + +#endif diff --git a/buildcc/lib/toolchain/include/toolchain/common/file_ext.h b/buildcc/lib/toolchain/include/toolchain/common/file_ext.h new file mode 100644 index 00000000..64a35660 --- /dev/null +++ b/buildcc/lib/toolchain/include/toolchain/common/file_ext.h @@ -0,0 +1,32 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAIN_COMMON_FILE_EXT_H_ +#define TOOLCHAIN_COMMON_FILE_EXT_H_ + +namespace buildcc { + +enum class FileExt { + Asm, ///< Valid Assembly source extension + C, ///< Valid C source extension + Cpp, ///< Valid Cpp source extension + Header, ///< Valid Header extension + Invalid, ///< Not a valid C/C++ family extension +}; + +} + +#endif diff --git a/buildcc/lib/toolchain/include/toolchain/common/toolchain_config.h b/buildcc/lib/toolchain/include/toolchain/common/toolchain_config.h new file mode 100644 index 00000000..8cdcce3b --- /dev/null +++ b/buildcc/lib/toolchain/include/toolchain/common/toolchain_config.h @@ -0,0 +1,101 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAIN_COMMON_TOOLCHAIN_CONFIG_H_ +#define TOOLCHAIN_COMMON_TOOLCHAIN_CONFIG_H_ + +#include +#include +#include + +#include "toolchain/common/file_ext.h" + +namespace fs = std::filesystem; + +namespace buildcc { + +struct ToolchainConfig { + ToolchainConfig() = default; + + /** + * @brief Get the valid file extension from a path + * + * See ToolchainConfig::valid_c_ext, ToolchainConfig::valid_cpp_ext, + * ToolchainConfig::valid_asm_ext, ToolchainConfig::valid_header_ext + * + * @param filepath Absolute / Relative path of the file + * @return FileExt File path detected as per Toolchain::valid_* + * variables + */ + FileExt GetFileExt(const fs::path &filepath) const; + + /** + * @brief Checks for C/C++ source file validity. + * + * See ToolchainConfig::valid_c_ext, ToolchainConfig::valid_cpp_ext, + * ToolchainConfig::valid_asm_ext + * + * @param filepath Absolute / Relative path of file + * @return true If file extension belongs to the above valid_* list + * @return false If file extension does not belong to the above valid_* list + */ + bool IsValidSource(const fs::path &filepath) const; + + /** + * @brief Checks for Header file validity + * + * See ToolchainConfig::valid_header_ext + * + * @param filepath Absolute / Relative path of file + * @return true If file extension belongs to above valid_* list + * @return false If file extension does not belong to above valid_* list + */ + bool IsValidHeader(const fs::path &filepath) const; + + /** + * @brief Expects Source file validity + * + * env::assert_fatal if not a valid source + * + * @param filepath Absolute / Relative path of file + */ + void ExpectsValidSource(const fs::path &filepath) const; + + /** + * @brief Expects header file validity + * + * env::assert_fatal if not a valid header + * + * @param filepath Absolute / Relative path of file + */ + void ExpectsValidHeader(const fs::path &filepath) const; + + std::string obj_ext{".o"}; + std::string pch_header_ext{".h"}; + std::string pch_compile_ext{".gch"}; + + std::string prefix_include_dir{"-I"}; + std::string prefix_lib_dir{"-L"}; + + std::unordered_set valid_c_ext{".c"}; + std::unordered_set valid_cpp_ext{".cpp", ".cxx", ".cc"}; + std::unordered_set valid_asm_ext{".s", ".S", ".asm"}; + std::unordered_set valid_header_ext{".h", ".hpp"}; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/toolchain/include/toolchain/common/toolchain_executables.h b/buildcc/lib/toolchain/include/toolchain/common/toolchain_executables.h new file mode 100644 index 00000000..b8862200 --- /dev/null +++ b/buildcc/lib/toolchain/include/toolchain/common/toolchain_executables.h @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAIN_COMMON_TOOLCHAIN_EXECUTABLES_H_ +#define TOOLCHAIN_COMMON_TOOLCHAIN_EXECUTABLES_H_ + +#include +#include + +namespace buildcc { + +struct ToolchainExecutables { + explicit ToolchainExecutables() = default; + explicit ToolchainExecutables(std::string_view as, std::string_view c, + std::string_view cpp, std::string_view ar, + std::string_view link) + : assembler(as), c_compiler(c), cpp_compiler(cpp), archiver(ar), + linker(link) {} + std::string assembler; + std::string c_compiler; + std::string cpp_compiler; + std::string archiver; + std::string linker; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/toolchain/include/toolchain/common/toolchain_id.h b/buildcc/lib/toolchain/include/toolchain/common/toolchain_id.h new file mode 100644 index 00000000..d19ea6d6 --- /dev/null +++ b/buildcc/lib/toolchain/include/toolchain/common/toolchain_id.h @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAIN_COMMON_TOOLCHAIN_ID_H_ +#define TOOLCHAIN_COMMON_TOOLCHAIN_ID_H_ + +#include "fmt/format.h" + +namespace buildcc { + +enum class ToolchainId { + Gcc = 0, ///< GCC Toolchain + Msvc, ///< MSVC Toolchain + Clang, ///< Clang Toolchain + MinGW, ///< MinGW Toolchain (Similar to GCC, but for Windows) + Custom, ///< Custom Toolchain not defined in this list + Undefined, ///< Default value when unknown +}; + +} // namespace buildcc + +template <> +struct fmt::formatter : formatter { + template + auto format(buildcc::ToolchainId id, FormatContext &ctx) { + std::string id_name; + switch (id) { + case buildcc::ToolchainId::Gcc: + id_name = "Gcc"; + break; + case buildcc::ToolchainId::Msvc: + id_name = "Msvc"; + break; + case buildcc::ToolchainId::Clang: + id_name = "Clang"; + break; + case buildcc::ToolchainId::MinGW: + id_name = "MinGW"; + break; + case buildcc::ToolchainId::Custom: + id_name = "Custom"; + break; + case buildcc::ToolchainId::Undefined: + default: + id_name = "Undefined"; + break; + } + return formatter::format(id_name, ctx); + } +}; + +#endif diff --git a/buildcc/lib/toolchain/include/toolchain/toolchain.h b/buildcc/lib/toolchain/include/toolchain/toolchain.h new file mode 100644 index 00000000..71e38e0e --- /dev/null +++ b/buildcc/lib/toolchain/include/toolchain/toolchain.h @@ -0,0 +1,103 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAIN_TOOLCHAIN_H_ +#define TOOLCHAIN_TOOLCHAIN_H_ + +#include +#include +#include + +#include "toolchain/common/toolchain_config.h" +#include "toolchain/common/toolchain_executables.h" +#include "toolchain/common/toolchain_id.h" + +#include "toolchain/api/flag_api.h" +#include "toolchain/api/toolchain_find.h" +#include "toolchain/api/toolchain_verify.h" + +namespace buildcc { + +// Base toolchain class +class Toolchain : public internal::FlagApi, + public ToolchainFind, + public ToolchainVerify { +public: + // TODO, Remove ToolchainId from here + Toolchain(ToolchainId id, std::string_view name, + const ToolchainExecutables &executables, + const ToolchainConfig &config = ToolchainConfig()) + : id_(id), name_(name), executables_(executables), config_(config) {} + + virtual ~Toolchain() = default; + Toolchain(Toolchain &&) = default; + Toolchain &operator=(Toolchain &&) = default; + Toolchain(const Toolchain &) = delete; + Toolchain &operator=(const Toolchain &) = delete; + + // Getters + ToolchainId GetId() const { return id_; } + const std::string &GetName() const { return name_; } + const std::string &GetAssembler() const { return executables_.assembler; } + const std::string &GetCCompiler() const { return executables_.c_compiler; } + const std::string &GetCppCompiler() const { + return executables_.cpp_compiler; + } + const std::string &GetArchiver() const { return executables_.archiver; } + const std::string &GetLinker() const { return executables_.linker; } + const ToolchainExecutables &GetToolchainExecutables() const { + return executables_; + } + + const ToolchainConfig &GetConfig() const { return config_; } + +protected: + ToolchainId &RefId() { return id_; } + std::string &RefName() { return name_; } + ToolchainExecutables &RefExecutables() { return executables_; } + ToolchainConfig &RefConfig() { return config_; } + +private: + struct UserSchema { + std::vector preprocessor_flags; + std::vector common_compile_flags; + std::vector pch_compile_flags; + std::vector pch_object_flags; + std::vector asm_compile_flags; + std::vector c_compile_flags; + std::vector cpp_compile_flags; + std::vector link_flags; + }; + +private: + friend class internal::FlagApi; + friend class ToolchainVerify; + +private: + ToolchainId id_; + std::string name_; + ToolchainExecutables executables_; + ToolchainConfig config_; + + // + UserSchema user_; +}; + +typedef Toolchain BaseToolchain; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/toolchain/src/api/toolchain_find.cpp b/buildcc/lib/toolchain/src/api/toolchain_find.cpp new file mode 100644 index 00000000..9c1e621e --- /dev/null +++ b/buildcc/lib/toolchain/src/api/toolchain_find.cpp @@ -0,0 +1,108 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "toolchain/api/toolchain_find.h" + +#include "env/assert_fatal.h" +#include "env/host_os.h" +#include "env/host_os_util.h" +#include "env/util.h" + +#include "toolchain/toolchain.h" + +namespace { + +std::vector ParseEnvVarToPaths(const std::string &env_var) { + const char *path_env = getenv(env_var.c_str()); + buildcc::env::assert_fatal( + path_env != nullptr, + fmt::format("Environment variable '{}' not present", env_var)); + + constexpr const char *os_env_delim = buildcc::env::get_os_envvar_delim(); + buildcc::env::assert_fatal("OS not supported"); + std::vector paths = + buildcc::env::split(path_env, os_env_delim[0]); + + return paths; +} + +bool ContainsToolchainExecutables( + const fs::directory_iterator &directory_iterator, + const buildcc::ToolchainExecutables &executables) { + std::unordered_set exes( + {executables.assembler, executables.c_compiler, executables.cpp_compiler, + executables.archiver, executables.linker}); + std::error_code ec; + for (const auto &dir_iter : directory_iterator) { + bool is_regular_file = dir_iter.is_regular_file(ec); + if (!is_regular_file || ec) { + continue; + } + const auto &filename_without_ext = dir_iter.path().stem().string(); + // NOTE, Must match the entire filename + exes.erase(filename_without_ext); + } + return exes.empty(); +} + +} // namespace + +namespace buildcc { + +template +std::vector +ToolchainFind::Find(const ToolchainFindConfig &config) const { + // Initialization + const T &t = static_cast(*this); + std::vector found_toolchains; + std::vector absolute_search_paths(config.absolute_search_paths); + + // Parse config envs and add it to absolute search paths + for (const auto &env_var : config.env_vars) { + std::vector paths = ParseEnvVarToPaths(env_var); + absolute_search_paths.insert(absolute_search_paths.end(), paths.begin(), + paths.end()); + } + + // Over the absolute search paths + // - Check if directory exists + // - Iterate over directory + // - Find ALL Toolchain binaries in ONE directory + // - If matched, store that path + for (const auto &search_path : absolute_search_paths) { + if (!fs::exists(search_path)) { + continue; + } + + std::error_code ec; + auto directory_iterator = fs::directory_iterator(search_path, ec); + if (ec) { + continue; + } + + bool toolchains_matched = ContainsToolchainExecutables( + directory_iterator, t.GetToolchainExecutables()); + if (toolchains_matched) { + found_toolchains.push_back(search_path); + } + } + + return found_toolchains; +} + +template class ToolchainFind; + +} // namespace buildcc diff --git a/buildcc/lib/toolchain/src/api/toolchain_verify.cpp b/buildcc/lib/toolchain/src/api/toolchain_verify.cpp new file mode 100644 index 00000000..7189ecb7 --- /dev/null +++ b/buildcc/lib/toolchain/src/api/toolchain_verify.cpp @@ -0,0 +1,112 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "toolchain/api/toolchain_verify.h" + +#include +#include + +#include + +#include "schema/path.h" + +#include "env/assert_fatal.h" +#include "env/host_os.h" +#include "env/host_os_util.h" +#include "env/util.h" + +#include "env/command.h" + +#include "toolchain/toolchain.h" + +namespace { + +buildcc::ToolchainExecutables CreateToolchainExecutables( + const fs::path &absolute_path, + const buildcc::ToolchainExecutables ¤t_executables) { + constexpr const char *const executable_ext = + buildcc::env::get_os_executable_extension(); + buildcc::env::assert_fatal( + "Host executable extension not supported"); + + std::string assembler_path = + (absolute_path / + fmt::format("{}{}", current_executables.assembler, executable_ext)) + .string(); + std::string c_compiler_path = + (absolute_path / + fmt::format("{}{}", current_executables.c_compiler, executable_ext)) + .string(); + std::string cpp_compiler_path = + (absolute_path / + fmt::format("{}{}", current_executables.cpp_compiler, executable_ext)) + .string(); + std::string archiver_path = + (absolute_path / + fmt::format("{}{}", current_executables.archiver, executable_ext)) + .string(); + std::string linker_path = + (absolute_path / + fmt::format("{}{}", current_executables.linker, executable_ext)) + .string(); + + return buildcc::ToolchainExecutables(assembler_path, c_compiler_path, + cpp_compiler_path, archiver_path, + linker_path); +} + +} // namespace + +namespace buildcc { + +template +ToolchainCompilerInfo +ToolchainVerify::Verify(const ToolchainFindConfig &config) { + T &t = static_cast(*this); + std::vector toolchain_paths = t.Find(config); + env::assert_fatal(!toolchain_paths.empty(), "No toolchains found"); + + ToolchainExecutables exes = + CreateToolchainExecutables(toolchain_paths[0], t.executables_); + env::optional op_toolchain_compiler_info{}; + if (GetToolchainInfoCb()) { + op_toolchain_compiler_info = GetToolchainInfoCb()(exes); + } + env::assert_fatal(op_toolchain_compiler_info.has_value(), + "Could not verify toolchain"); + + ToolchainCompilerInfo toolchain_compiler_info = + op_toolchain_compiler_info.value(); + toolchain_compiler_info.path = toolchain_paths[0]; + + // Update the compilers + t.executables_ = exes; + return toolchain_compiler_info; +} + +template +void ToolchainVerify::SetToolchainInfoCb(const ToolchainInfoCb &cb) { + info_cb_ = cb; +} + +template +const ToolchainInfoCb &ToolchainVerify::GetToolchainInfoCb() const { + return info_cb_; +} + +template class ToolchainVerify; + +} // namespace buildcc diff --git a/buildcc/lib/toolchain/src/common/toolchain_config.cpp b/buildcc/lib/toolchain/src/common/toolchain_config.cpp new file mode 100644 index 00000000..55125dd4 --- /dev/null +++ b/buildcc/lib/toolchain/src/common/toolchain_config.cpp @@ -0,0 +1,88 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "toolchain/common/toolchain_config.h" + +#include "env/assert_fatal.h" + +#include "schema/path.h" + +#include "fmt/format.h" + +namespace buildcc { + +FileExt ToolchainConfig::GetFileExt(const fs::path &filepath) const { + if (!filepath.has_extension()) { + return FileExt::Invalid; + } + + FileExt type = FileExt::Invalid; + const std::string ext = filepath.extension().string(); + + if (valid_c_ext.count(ext) == 1) { + type = FileExt::C; + } else if (valid_cpp_ext.count(ext) == 1) { + type = FileExt::Cpp; + } else if (valid_asm_ext.count(ext) == 1) { + type = FileExt::Asm; + } else if (valid_header_ext.count(ext) == 1) { + type = FileExt::Header; + } + + return type; +} + +bool ToolchainConfig::IsValidSource(const fs::path &filepath) const { + if (!filepath.has_extension()) { + return false; + } + + const std::string ext = filepath.extension().string(); + bool valid = false; + if ((valid_c_ext.find(ext) != valid_c_ext.end()) || + (valid_cpp_ext.find(ext) != valid_cpp_ext.end()) || + (valid_asm_ext.find(ext) != valid_asm_ext.end())) { + valid = true; + } + return valid; +} + +void ToolchainConfig::ExpectsValidSource(const fs::path &filepath) const { + env::assert_fatal( + IsValidSource(filepath), + fmt::format("{} does not have a valid source extension", filepath)); +} + +bool ToolchainConfig::IsValidHeader(const fs::path &filepath) const { + if (!filepath.has_extension()) { + return {}; + } + + const std::string ext = filepath.extension().string(); + bool valid = false; + if ((valid_header_ext.find(ext) != valid_header_ext.end())) { + valid = true; + } + return valid; +} + +void ToolchainConfig::ExpectsValidHeader(const fs::path &filepath) const { + env::assert_fatal( + IsValidHeader(filepath), + fmt::format("{} does not have a valid header extension", filepath)); +} + +} // namespace buildcc diff --git a/buildcc/lib/toolchain/toolchain.cpp b/buildcc/lib/toolchain/src/toolchain/toolchain.cpp similarity index 78% rename from buildcc/lib/toolchain/toolchain.cpp rename to buildcc/lib/toolchain/src/toolchain/toolchain.cpp index 1b982d75..3c5a3141 100644 --- a/buildcc/lib/toolchain/toolchain.cpp +++ b/buildcc/lib/toolchain/src/toolchain/toolchain.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,8 @@ * limitations under the License. */ -#include "toolchain.h" +#include "toolchain/toolchain.h" + +#include + +namespace buildcc {} // namespace buildcc diff --git a/buildcc/lib/toolchain/test/test_toolchain_config.cpp b/buildcc/lib/toolchain/test/test_toolchain_config.cpp new file mode 100644 index 00000000..a0cfce24 --- /dev/null +++ b/buildcc/lib/toolchain/test/test_toolchain_config.cpp @@ -0,0 +1,41 @@ +#include "toolchain/common/toolchain_config.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" + +// clang-format off +TEST_GROUP(ToolchainConfigTestGroup) +{ +}; +// clang-format on + +TEST(ToolchainConfigTestGroup, GetFileExt) { + buildcc::ToolchainConfig toolchain_config; + + buildcc::FileExt ext; + + ext = toolchain_config.GetFileExt("file.asm"); + CHECK(ext == buildcc::FileExt::Asm); + + ext = toolchain_config.GetFileExt("file.c"); + CHECK(ext == buildcc::FileExt::C); + + ext = toolchain_config.GetFileExt("file.cpp"); + CHECK(ext == buildcc::FileExt::Cpp); + + ext = toolchain_config.GetFileExt("file.h"); + CHECK(ext == buildcc::FileExt::Header); + + ext = toolchain_config.GetFileExt("file.invalid"); + CHECK(ext == buildcc::FileExt::Invalid); + + ext = toolchain_config.GetFileExt("random/directory"); + CHECK(ext == buildcc::FileExt::Invalid); +} + +int main(int ac, char **av) { + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/toolchain/test/test_toolchain_find.cpp b/buildcc/lib/toolchain/test/test_toolchain_find.cpp new file mode 100644 index 00000000..9657dfb9 --- /dev/null +++ b/buildcc/lib/toolchain/test/test_toolchain_find.cpp @@ -0,0 +1,107 @@ +#include + +#include "toolchain/toolchain.h" + +#include "env/host_os.h" + +#include "expect_command.h" + +#include "mock_command_copier.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(ToolchainFindTestGroup) +{ + void teardown() { + mock().checkExpectations(); + mock().clear(); + } +}; +// clang-format on + +TEST(ToolchainFindTestGroup, FindToolchain_ThroughEnvVar) { + buildcc::Toolchain gcc( + buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", "ar", "ld")); + + std::string putenv_str = fmt::format("CUSTOM_BUILDCC_PATH={}/toolchains/gcc", + fs::current_path().string()); + int put = putenv(putenv_str.data()); + CHECK_TRUE(put == 0); + const char *custom_buildcc_path = getenv("CUSTOM_BUILDCC_PATH"); + CHECK_TRUE(custom_buildcc_path != nullptr); + UT_PRINT(custom_buildcc_path); + + buildcc::ToolchainFindConfig config; + config.env_vars.clear(); + config.env_vars.push_back("CUSTOM_BUILDCC_PATH"); + + std::vector found_toolchains = gcc.Find(config); + CHECK_TRUE(!found_toolchains.empty()); +} + +TEST(ToolchainFindTestGroup, FindToolchain_ThroughAbsolutePath) { + buildcc::Toolchain gcc( + buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", "ar", "ld")); + + buildcc::ToolchainFindConfig config; + config.absolute_search_paths.push_back(fs::current_path() / "toolchains" / + "gcc"); + config.env_vars.clear(); + + std::vector found_toolchains = gcc.Find(config); + CHECK_TRUE(!found_toolchains.empty()); +} + +TEST(ToolchainFindTestGroup, FindToolchain_DirectoryDoesntExist) { + buildcc::Toolchain gcc( + buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", "ar", "ld")); + + buildcc::ToolchainFindConfig config; + config.absolute_search_paths.push_back(fs::current_path() / "toolchains" / + "directory_doesnt_exist"); + config.env_vars.clear(); + + std::vector found_toolchains = gcc.Find(config); + CHECK_TRUE(found_toolchains.empty()); +} + +TEST(ToolchainFindTestGroup, FindToolchain_NoDirectoryFound) { + buildcc::Toolchain gcc( + buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", "ar", "ld")); + + buildcc::ToolchainFindConfig config; + config.absolute_search_paths.push_back(fs::current_path() / "toolchains" / + "gcc" / "ar"); + config.env_vars.clear(); + + std::vector found_toolchains = gcc.Find(config); + CHECK_TRUE(found_toolchains.empty()); +} + +TEST(ToolchainFindTestGroup, FindToolchain_NoToolchainFound) { + buildcc::Toolchain gcc( + buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", "ar", "ld")); + + buildcc::ToolchainFindConfig config; + config.absolute_search_paths.push_back(fs::current_path() / "toolchains"); + config.env_vars.clear(); + + std::vector found_toolchains = gcc.Find(config); + CHECK_TRUE(found_toolchains.empty()); +} + +int main(int ac, char **av) { + MemoryLeakWarningPlugin::turnOffNewDeleteOverloads(); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/toolchain/test/test_toolchain_id.cpp b/buildcc/lib/toolchain/test/test_toolchain_id.cpp new file mode 100644 index 00000000..6cb77e6b --- /dev/null +++ b/buildcc/lib/toolchain/test/test_toolchain_id.cpp @@ -0,0 +1,50 @@ +#include + +#include "toolchain/common/toolchain_id.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(ToolchainIdTestGroup) +{ + void teardown() { + mock().checkExpectations(); + mock().clear(); + } +}; +// clang-format on + +TEST(ToolchainIdTestGroup, ToolchainIdAsString) { + std::string compiler; + + compiler = fmt::format("{}", buildcc::ToolchainId::Gcc); + STRCMP_EQUAL(compiler.c_str(), "Gcc"); + + compiler = fmt::format("{}", buildcc::ToolchainId::Msvc); + STRCMP_EQUAL(compiler.c_str(), "Msvc"); + + compiler = fmt::format("{}", buildcc::ToolchainId::Clang); + STRCMP_EQUAL(compiler.c_str(), "Clang"); + + compiler = fmt::format("{}", buildcc::ToolchainId::MinGW); + STRCMP_EQUAL(compiler.c_str(), "MinGW"); + + compiler = fmt::format("{}", buildcc::ToolchainId::Custom); + STRCMP_EQUAL(compiler.c_str(), "Custom"); + + compiler = fmt::format("{}", buildcc::ToolchainId::Undefined); + STRCMP_EQUAL(compiler.c_str(), "Undefined"); + + compiler = fmt::format("{}", (buildcc::ToolchainId)65535); + STRCMP_EQUAL(compiler.c_str(), "Undefined"); +} + +int main(int ac, char **av) { + MemoryLeakWarningPlugin::turnOffNewDeleteOverloads(); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/toolchain/test/test_toolchain_verify.cpp b/buildcc/lib/toolchain/test/test_toolchain_verify.cpp new file mode 100644 index 00000000..697fb45c --- /dev/null +++ b/buildcc/lib/toolchain/test/test_toolchain_verify.cpp @@ -0,0 +1,157 @@ +#include + +#include "toolchain/toolchain.h" + +#include "env/command.h" +#include "env/host_os.h" + +#include "expect_command.h" + +#include "mock_command_copier.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(ToolchainVerifyTestGroup) +{ + void teardown() { + mock().checkExpectations(); + mock().clear(); + } +}; +// clang-format on + +class MockToolchain : public buildcc::Toolchain { +public: + MockToolchain(buildcc::ToolchainId id, const std::string &name, + const buildcc::ToolchainExecutables &executables = + buildcc::ToolchainExecutables("as", "gcc", "g++", "ar", + "ld")) + : buildcc::Toolchain(id, name, executables) {} +}; + +// NOTE, We are mocking the environment instead of actually querying it +TEST(ToolchainVerifyTestGroup, VerifyToolchain_BaseToolchain_Failure) { + MockToolchain gcc( + buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", "ar", "ld")); + + std::string putenv_str = + fmt::format("CUSTOM_BUILDCC_PATH={}/toolchains/gcc", fs::current_path()); + int put = putenv(putenv_str.data()); + CHECK_TRUE(put == 0); + const char *custom_buildcc_path = getenv("CUSTOM_BUILDCC_PATH"); + CHECK_TRUE(custom_buildcc_path != nullptr); + UT_PRINT(custom_buildcc_path); + + buildcc::ToolchainFindConfig config; + config.env_vars.clear(); + config.env_vars.push_back("CUSTOM_BUILDCC_PATH"); + + CHECK_THROWS(std::exception, gcc.Verify(config)); +} + +TEST(ToolchainVerifyTestGroup, VerifyToolchain_BadAbsolutePath) { + MockToolchain gcc(buildcc::ToolchainId::Gcc, "gcc"); + + buildcc::ToolchainFindConfig config; + config.env_vars.clear(); + config.absolute_search_paths.push_back( + (fs::current_path() / "does_not_exist")); + + CHECK_THROWS(std::exception, gcc.Verify(config)); +} + +TEST(ToolchainVerifyTestGroup, VerifyToolchain_PathContainsDir) { + MockToolchain gcc(buildcc::ToolchainId::Gcc, "gcc"); + + buildcc::ToolchainFindConfig config; + config.env_vars.clear(); + config.absolute_search_paths.push_back((fs::current_path() / "toolchains")); + + CHECK_THROWS(std::exception, gcc.Verify(config)); +} + +#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(__MINGW64__) + +TEST(ToolchainVerifyTestGroup, VerifyToolchain_LockedFolder) { + std::error_code err; + fs::permissions(fs::current_path() / "toolchains" / "gcc", fs::perms::none, + err); + if (err) { + FAIL_TEST("Could not set file permissions"); + } + + MockToolchain gcc(buildcc::ToolchainId::Gcc, "gcc"); + + buildcc::ToolchainFindConfig config; + config.env_vars.clear(); + config.absolute_search_paths.push_back( + (fs::current_path() / "toolchains" / "gcc")); + + CHECK_THROWS(std::exception, gcc.Verify(config)); + + fs::permissions(fs::current_path() / "toolchains" / "gcc", fs::perms::all, + err); + if (err) { + FAIL_TEST("Could not set file permissions"); + } +} + +#endif + +TEST(ToolchainVerifyTestGroup, CustomToolchainInfo) { + buildcc::Toolchain toolchain( + buildcc::ToolchainId::Gcc, "gcc", + buildcc::ToolchainExecutables("as", "gcc", "g++", "ar", "ld")); + toolchain.SetToolchainInfoCb( + [](const buildcc::ToolchainExecutables &executables) + -> buildcc::env::optional { + (void)executables; + mock().actualCall("SetToolchainInfoCb"); + buildcc::ToolchainCompilerInfo info; + info.compiler_version = "version"; + info.target_arch = "arch"; + return info; + }); + + std::string putenv_str = + fmt::format("CUSTOM_BUILDCC_PATH={}/toolchains/gcc", fs::current_path()); + int put = putenv(putenv_str.data()); + CHECK_TRUE(put == 0); + const char *custom_buildcc_path = getenv("CUSTOM_BUILDCC_PATH"); + CHECK_TRUE(custom_buildcc_path != nullptr); + UT_PRINT(custom_buildcc_path); + + buildcc::ToolchainFindConfig config; + config.env_vars.clear(); + config.env_vars.push_back("CUSTOM_BUILDCC_PATH"); + + mock().expectOneCall("SetToolchainInfoCb"); + auto info = toolchain.Verify(config); + STRCMP_EQUAL(info.compiler_version.c_str(), "version"); + STRCMP_EQUAL(info.target_arch.c_str(), "arch"); +} + +int main(int ac, char **av) { + buildcc::env::m::VectorStringCopier copier; + mock().installCopier(TEST_VECTOR_STRING_TYPE, copier); + + // NOTE, Check the GCC, MSVC and Clang compilers + // Create directory and populate it with gcc and cl executables + // Linux + // toolchains/gcc + // toolchains/clang + + // Windows + // toolchains/msvc + // toolchains/mingw + // TODO, Check executables used in clang + MemoryLeakWarningPlugin::turnOffNewDeleteOverloads(); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/lib/toolchain/test/toolchains/clang/clang b/buildcc/lib/toolchain/test/toolchains/clang/clang new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/clang/clang++ b/buildcc/lib/toolchain/test/toolchains/clang/clang++ new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/clang/clang++.exe b/buildcc/lib/toolchain/test/toolchains/clang/clang++.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/clang/clang.exe b/buildcc/lib/toolchain/test/toolchains/clang/clang.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/clang/lld b/buildcc/lib/toolchain/test/toolchains/clang/lld new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/clang/lld.exe b/buildcc/lib/toolchain/test/toolchains/clang/lld.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/clang/llvm-ar b/buildcc/lib/toolchain/test/toolchains/clang/llvm-ar new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/clang/llvm-ar.exe b/buildcc/lib/toolchain/test/toolchains/clang/llvm-ar.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/clang/llvm-as b/buildcc/lib/toolchain/test/toolchains/clang/llvm-as new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/clang/llvm-as.exe b/buildcc/lib/toolchain/test/toolchains/clang/llvm-as.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/custom/archiver b/buildcc/lib/toolchain/test/toolchains/custom/archiver new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/custom/archiver.exe b/buildcc/lib/toolchain/test/toolchains/custom/archiver.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/custom/assembler b/buildcc/lib/toolchain/test/toolchains/custom/assembler new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/custom/assembler.exe b/buildcc/lib/toolchain/test/toolchains/custom/assembler.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/custom/c_compiler b/buildcc/lib/toolchain/test/toolchains/custom/c_compiler new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/custom/c_compiler.exe b/buildcc/lib/toolchain/test/toolchains/custom/c_compiler.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/custom/cpp_compiler b/buildcc/lib/toolchain/test/toolchains/custom/cpp_compiler new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/custom/cpp_compiler.exe b/buildcc/lib/toolchain/test/toolchains/custom/cpp_compiler.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/custom/linker b/buildcc/lib/toolchain/test/toolchains/custom/linker new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/custom/linker.exe b/buildcc/lib/toolchain/test/toolchains/custom/linker.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/gcc/ar b/buildcc/lib/toolchain/test/toolchains/gcc/ar new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/gcc/ar.exe b/buildcc/lib/toolchain/test/toolchains/gcc/ar.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/gcc/as b/buildcc/lib/toolchain/test/toolchains/gcc/as new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/gcc/as.exe b/buildcc/lib/toolchain/test/toolchains/gcc/as.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/gcc/g++ b/buildcc/lib/toolchain/test/toolchains/gcc/g++ new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/gcc/g++.exe b/buildcc/lib/toolchain/test/toolchains/gcc/g++.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/gcc/gcc b/buildcc/lib/toolchain/test/toolchains/gcc/gcc new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/gcc/gcc.exe b/buildcc/lib/toolchain/test/toolchains/gcc/gcc.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/gcc/ld b/buildcc/lib/toolchain/test/toolchains/gcc/ld new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/gcc/ld.exe b/buildcc/lib/toolchain/test/toolchains/gcc/ld.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/msvc/cl b/buildcc/lib/toolchain/test/toolchains/msvc/cl new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/msvc/cl.exe b/buildcc/lib/toolchain/test/toolchains/msvc/cl.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/msvc/lib b/buildcc/lib/toolchain/test/toolchains/msvc/lib new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/msvc/lib.exe b/buildcc/lib/toolchain/test/toolchains/msvc/lib.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/msvc/link b/buildcc/lib/toolchain/test/toolchains/msvc/link new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/test/toolchains/msvc/link.exe b/buildcc/lib/toolchain/test/toolchains/msvc/link.exe new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/lib/toolchain/toolchain.h b/buildcc/lib/toolchain/toolchain.h deleted file mode 100644 index 377a3714..00000000 --- a/buildcc/lib/toolchain/toolchain.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TOOLCHAIN_TOOLCHAIN_H_ -#define TOOLCHAIN_TOOLCHAIN_H_ - -#include -#include -#include - -namespace buildcc::base { - -// Base toolchain class -class Toolchain { -public: - explicit Toolchain(std::string_view name, std::string_view asm_compiler, - std::string_view c_compiler, std::string_view cpp_compiler, - std::string_view archiver, std::string_view linker) - : name_(name), asm_compiler_(asm_compiler), c_compiler_(c_compiler), - cpp_compiler_(cpp_compiler), archiver_(archiver), linker_(linker) {} - - Toolchain(const Toolchain &toolchain) = delete; - - // Getters - const std::string &GetName() const { return name_; } - const std::string &GetAsmCompiler() const { return asm_compiler_; } - const std::string &GetCCompiler() const { return c_compiler_; } - const std::string &GetCppCompiler() const { return cpp_compiler_; } - const std::string &GetArchiver() const { return archiver_; } - const std::string &GetLinker() const { return linker_; } - -private: - std::string name_; - std::string asm_compiler_; - std::string c_compiler_; - std::string cpp_compiler_; - std::string archiver_; - std::string linker_; -}; - -} // namespace buildcc::base - -#endif diff --git a/buildcc/plugins/CMakeLists.txt b/buildcc/plugins/CMakeLists.txt index 4224f4b2..42e4700d 100644 --- a/buildcc/plugins/CMakeLists.txt +++ b/buildcc/plugins/CMakeLists.txt @@ -1,20 +1,76 @@ -m_clangtidy("plugins") -add_library(plugins - clang_compile_commands.cpp - clang_compile_commands.h +# Plugin test +if (${TESTING}) +add_library(mock_plugins + src/buildcc_find.cpp ) -target_include_directories(plugins PUBLIC - $ - $ +target_include_directories(mock_plugins PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_link_libraries(plugins PRIVATE target) +target_compile_options(mock_plugins PUBLIC + ${TEST_COMPILE_FLAGS} ${BUILD_COMPILE_FLAGS} +) +target_link_options(mock_plugins PUBLIC + ${TEST_LINK_FLAGS} ${BUILD_LINK_FLAGS} +) +target_link_libraries(mock_plugins PUBLIC + mock_target -# Target_gcc Install -if (${BUILDCC_INSTALL}) - install(TARGETS plugins DESTINATION lib EXPORT pluginsConfig) - install(FILES - ${CMAKE_CURRENT_SOURCE_DIR}/clang_compile_commands.h - DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}" + CppUTest + CppUTestExt + ${TEST_LINK_LIBS} +) + +# Tests +# Removed test_buildcc_find till it is not complete +# add_executable(test_buildcc_find +# test/test_buildcc_find.cpp +# ) +# target_link_libraries(test_buildcc_find PRIVATE +# mock_plugins +# ) + +# add_test(NAME test_buildcc_find COMMAND test_buildcc_find +# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test +# ) + +endif() + +set(PLUGINS_SRCS + src/clang_compile_commands.cpp + src/buildcc_find.cpp + include/plugins/clang_compile_commands.h + include/plugins/buildcc_find.h +) + +if(${BUILDCC_BUILD_AS_SINGLE_LIB}) + target_sources(buildcc PRIVATE + ${PLUGINS_SRCS} + ) + target_include_directories(buildcc PUBLIC + $ + $ + ) +endif() + +if (${BUILDCC_BUILD_AS_INTERFACE}) + m_clangtidy("plugins") + add_library(plugins + ${PLUGINS_SRCS} ) - install(EXPORT pluginsConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/plugins") + target_include_directories(plugins PUBLIC + $ + $ + ) + target_link_libraries(plugins PRIVATE target) + + # Target_gcc Install + if (${BUILDCC_INSTALL}) + install(TARGETS plugins DESTINATION lib EXPORT pluginsConfig) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}") + install(EXPORT pluginsConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/plugins") + endif() +endif() + +if (${BUILDCC_INSTALL}) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}") endif() diff --git a/buildcc/plugins/clang_compile_commands.cpp b/buildcc/plugins/clang_compile_commands.cpp deleted file mode 100644 index e887ef52..00000000 --- a/buildcc/plugins/clang_compile_commands.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "clang_compile_commands.h" - -#include - -// env -#include "assert_fatal.h" - -// target -#include "internal/util.h" - -// flatbuffers -#include "flatbuffers/flexbuffers.h" - -namespace buildcc::plugin { - -void ClangCompileCommands::AddTarget(const base::Target *target) { - env::assert_fatal(target != nullptr, "Target should not be NULL"); - targets_.push_back(target); -} - -void ClangCompileCommands::Generate() { - // Early terminate if rebuild is not required - const bool regenerate = std::any_of( - targets_.begin(), targets_.end(), [](const base::Target *target) { - return (target->FirstBuild() || target->Rebuild()); - }); - if (!regenerate) { - env::log_trace("ClangCompileCommands", "Generate -> false"); - return; - } - env::log_trace("ClangCompileCommands", "Generate -> true"); - - // DONE, Create custom flexbuffer binary compile_commands interface - flexbuffers::Builder fbb; - size_t start = fbb.StartVector(); - - for (const auto *t : targets_) { - const auto &source_files = t->GetCurrentSourceFiles(); - - for (const auto &f : source_files) { - // DONE, Get source list name - // DONE, Get std::vector CompileCommand - // DONE, Get intermediate directory from env - const auto input_file = f.GetPathname(); - const auto command = t->CompileCommand(input_file); - const auto directory = env::get_project_build_dir(); - - // DONE, Use flatbuffers::Flexbuffer to create binary format - fbb.Map([&]() { - fbb.String("directory", directory.string()); - fbb.String("command", internal::aggregate(command)); - fbb.String("file", input_file.string()); - }); - } - } - - fbb.EndVector(start, false, false); - fbb.Finish(); - - // DONE, Convert to json - std::string compile_commands; - flexbuffers::GetRoot(fbb.GetBuffer()).ToString(true, true, compile_commands); - - // DONE, Save file using the flatbuffers::SaveFile utility function - std::filesystem::path file = - std::filesystem::path(buildcc::env::get_project_build_dir()) / - "compile_commands.json"; - bool saved = - flatbuffers::SaveFile(file.string().c_str(), compile_commands, false); - env::assert_fatal(saved, "Could not save compile_commands.json"); -} - -} // namespace buildcc::plugin diff --git a/buildcc/plugins/include/plugins/buildcc_find.h b/buildcc/plugins/include/plugins/buildcc_find.h new file mode 100644 index 00000000..50eab64e --- /dev/null +++ b/buildcc/plugins/include/plugins/buildcc_find.h @@ -0,0 +1,90 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PLUGINS_BUILDCC_FIND_H_ +#define PLUGINS_BUILDCC_FIND_H_ + +#include +#include +#include +#include + +#include "env/host_os.h" + +#include "fmt/format.h" + +namespace fs = std::filesystem; + +namespace buildcc::plugin { + +/** + * @brief By default searched the `PATH` environment variable for required + * package + * + */ +// TODO, Add support for regex matching +class BuildccFind { +public: + // TODO, Add more + enum class Type { + HostExecutable, + BuildccLibrary, + BuildccPlugin, + }; + +public: + BuildccFind(const std::string ®ex, Type type, + std::initializer_list host_env_vars = {}) + : regex_(regex), type_(type), host_env_vars_(host_env_vars) { + if constexpr (env::is_win()) { + regex_ = fmt::format("{}.exe", regex); + } + } + BuildccFind(const BuildccFind &) = delete; + + void AddHostEnvVar(const std::string &host_env_var) { + host_env_vars_.push_back(host_env_var); + } + void AddHostEnvVars(std::initializer_list host_env_vars) { + for (const auto &ev : host_env_vars) { + AddHostEnvVar(ev); + } + } + + /** + * @brief Starts the search for package of type and name + * + */ + bool Search(); + + // Getters + const std::vector &GetPathMatches() { return target_matches_; } + +private: + bool SearchHostExecutable(); + +private: + std::string regex_; + Type type_; + + std::vector host_env_vars_; + + std::vector target_matches_; +}; + +} // namespace buildcc::plugin + +#endif diff --git a/buildcc/plugins/clang_compile_commands.h b/buildcc/plugins/include/plugins/clang_compile_commands.h similarity index 67% rename from buildcc/plugins/clang_compile_commands.h rename to buildcc/plugins/include/plugins/clang_compile_commands.h index 19583cb0..a228e8cb 100644 --- a/buildcc/plugins/clang_compile_commands.h +++ b/buildcc/plugins/include/plugins/clang_compile_commands.h @@ -1,5 +1,5 @@ /* - * Copyright 2021 Niket Naidu. All rights reserved. + * Copyright 2021-2022 Niket Naidu. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,22 +17,29 @@ #ifndef PLUGINS_CLANG_COMPILE_COMMANDS_H_ #define PLUGINS_CLANG_COMPILE_COMMANDS_H_ -#include "target.h" +#include "target/target.h" namespace buildcc::plugin { class ClangCompileCommands { public: - explicit ClangCompileCommands(std::vector targets) - : targets_(std::move(targets)) {} + explicit ClangCompileCommands(std::vector &&targets) + : targets_(targets) {} ClangCompileCommands(const ClangCompileCommands &compile_commands) = delete; - void AddTarget(const base::Target *target); + /** + * @brief Add non-null targets + */ + void AddTarget(const BaseTarget *target); + /** + * @brief Generate clang compile commands file in `Project::GetBuildDir` + * folder + */ void Generate(); private: - std::vector targets_; + std::vector targets_; }; } // namespace buildcc::plugin diff --git a/buildcc/plugins/src/buildcc_find.cpp b/buildcc/plugins/src/buildcc_find.cpp new file mode 100644 index 00000000..d60eaa34 --- /dev/null +++ b/buildcc/plugins/src/buildcc_find.cpp @@ -0,0 +1,115 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "plugins/buildcc_find.h" + +#include +#include +#include + +#include "env/host_os_util.h" +#include "env/logging.h" +#include "env/util.h" + +#include "schema/path.h" + +namespace { +constexpr const char *const kEnvVarNotFound = + "{} environment variable not found"; +constexpr const char *const kOsNotSupported = + "Search functionality does not support this operating system. Raise an " + "issue at https://github.com/coder137/build_in_cpp outlining your OS and " + "usecase"; + +std::vector SearchEnv(const std::string &host_env_var, + const std::string ®ex) { + char *path_ptr = std::getenv(host_env_var.c_str()); + if (path_ptr == nullptr) { + buildcc::env::log_critical(__FUNCTION__, + fmt::format(kEnvVarNotFound, host_env_var)); + return {}; + } + + constexpr const char *const kDelim = buildcc::env::get_os_envvar_delim(); + if constexpr (kDelim == nullptr) { + buildcc::env::log_critical(__FUNCTION__, kOsNotSupported); + return {}; + } + + std::vector env_paths = buildcc::env::split(path_ptr, kDelim[0]); + + // DONE, Construct a directory iterator + // Only take the files + std::vector matches; + for (const auto &env_p : env_paths) { + std::error_code errcode; + const auto dir_iter = fs::directory_iterator(env_p, errcode); + if (errcode) { + buildcc::env::log_critical(env_p, errcode.message()); + continue; + } + + for (const auto &dir_entry : dir_iter) { + if (!dir_entry.path().has_filename()) { + continue; + } + + // Compare name_ with filename + std::string filename = dir_entry.path().filename().string(); + bool match = std::regex_match(filename, std::regex(regex)); + if (match) { + matches.push_back(dir_entry.path()); + } + } + } + + return matches; +} + +} // namespace + +namespace buildcc::plugin { + +// Public + +bool BuildccFind::Search() { + switch (type_) { + case Type::HostExecutable: + return SearchHostExecutable(); + break; + case Type::BuildccLibrary: + case Type::BuildccPlugin: + default: + // TODO, Add this functionality + break; + } + + return false; +} + +// Private + +bool BuildccFind::SearchHostExecutable() { + for (const auto &ev : host_env_vars_) { + std::vector matches = SearchEnv(ev, regex_); + target_matches_.insert(target_matches_.end(), matches.begin(), + matches.end()); + } + + return !target_matches_.empty(); +} + +} // namespace buildcc::plugin diff --git a/buildcc/plugins/src/clang_compile_commands.cpp b/buildcc/plugins/src/clang_compile_commands.cpp new file mode 100644 index 00000000..d3cd92e6 --- /dev/null +++ b/buildcc/plugins/src/clang_compile_commands.cpp @@ -0,0 +1,91 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "plugins/clang_compile_commands.h" + +#include + +// env +#include "env/assert_fatal.h" +#include "env/util.h" + +// third party +#include "fmt/format.h" + +namespace { + +// clang-format off +constexpr const char *const clang_compile_command_format = +R"({{ + "directory": "{directory}", + "command": "{command}", + "file": "{file}" +}})"; +// clang-format on + +} // namespace + +namespace buildcc::plugin { + +void ClangCompileCommands::AddTarget(const BaseTarget *target) { + env::assert_fatal(target != nullptr, "Target should not be NULL"); + targets_.push_back(target); +} + +void ClangCompileCommands::Generate() { + // Early terminate if rebuild is not required + const bool regenerate = + std::any_of(targets_.begin(), targets_.end(), + [](const BaseTarget *target) { return target->IsBuilt(); }); + if (!regenerate) { + env::log_trace("ClangCompileCommands", "Generate -> false"); + return; + } + env::log_trace("ClangCompileCommands", "Generate -> true"); + + std::vector compile_command_list; + + for (const auto *t : targets_) { + const auto &source_files = t->GetSourceFiles(); + for (const auto &source : source_files) { + // DONE, Get source list name + // DONE, Get std::vector CompileCommand + // DONE, Get intermediate directory from env + std::string sf = fmt::format("{}", source); + const std::string &command = t->GetCompileCommand(source); + std::string directory = fmt::format("{}", Project::GetBuildDir()); + + std::string temp = fmt::format( + clang_compile_command_format, fmt::arg("directory", directory), + fmt::arg("command", command), fmt::arg("file", sf)); + compile_command_list.push_back(temp); + } + } + + std::string compile_commands = + fmt::format("[\n{}\n]", fmt::join(compile_command_list, ",\n")); + + // DONE, Convert to json + // DONE, Save file + std::filesystem::path file = + std::filesystem::path(buildcc::Project::GetBuildDir()) / + "compile_commands.json"; + bool saved = + env::save_file(path_as_string(file).c_str(), compile_commands, false); + env::assert_fatal(saved, "Could not save compile_commands.json"); +} + +} // namespace buildcc::plugin diff --git a/buildcc/plugins/test/test_buildcc_find.cpp b/buildcc/plugins/test/test_buildcc_find.cpp new file mode 100644 index 00000000..5c1fee98 --- /dev/null +++ b/buildcc/plugins/test/test_buildcc_find.cpp @@ -0,0 +1,71 @@ +#include "plugins/buildcc_find.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" + +// clang-format off +TEST_GROUP(PluginsTestGroup) +{ +}; +// clang-format on + +TEST(PluginsTestGroup, BuildccFind_Search) { + buildcc::plugin::BuildccFind findcmake( + "cmake", buildcc::plugin::BuildccFind::Type::HostExecutable, {"PATH"}); + bool found = findcmake.Search(); + CHECK_TRUE(found); + + const std::vector &matches = findcmake.GetPathMatches(); + CHECK_TRUE(!matches.empty()); +} + +TEST(PluginsTestGroup, BuildccFind_BadEnv) { + buildcc::plugin::BuildccFind findrandomenv( + "cmake", buildcc::plugin::BuildccFind::Type::HostExecutable, + {"FIND_RANDOM_ENV"}); + bool found = findrandomenv.Search(); + CHECK_FALSE(found); + + const std::vector &matches = findrandomenv.GetPathMatches(); + CHECK_TRUE(matches.empty()); +} + +TEST(PluginsTestGroup, BuildccFind_WrongExecutable) { + buildcc::plugin::BuildccFind findrandomexecutable( + "random_cmake_executable", + buildcc::plugin::BuildccFind::Type::HostExecutable, {"FIND_RANDOM_ENV"}); + bool found = findrandomexecutable.Search(); + CHECK_FALSE(found); + + const std::vector &matches = findrandomexecutable.GetPathMatches(); + CHECK_TRUE(matches.empty()); +} + +TEST(PluginsTestGroup, BuildccFind_SearchUnimplemented) { + { + buildcc::plugin::BuildccFind findunimplemented( + "random_library", buildcc::plugin::BuildccFind::Type::BuildccLibrary); + bool found = findunimplemented.Search(); + CHECK_FALSE(found); + + const std::vector &matches = findunimplemented.GetPathMatches(); + CHECK_TRUE(matches.empty()); + } + + { + buildcc::plugin::BuildccFind findunimplemented( + "random_library", buildcc::plugin::BuildccFind::Type::BuildccPlugin); + bool found = findunimplemented.Search(); + CHECK_FALSE(found); + + const std::vector &matches = findunimplemented.GetPathMatches(); + CHECK_TRUE(matches.empty()); + } +} + +int main(int ac, char **av) { + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/schema/CMakeLists.txt b/buildcc/schema/CMakeLists.txt new file mode 100644 index 00000000..2f953e53 --- /dev/null +++ b/buildcc/schema/CMakeLists.txt @@ -0,0 +1 @@ +include(cmake/schema.cmake) diff --git a/buildcc/schema/cmake/schema.cmake b/buildcc/schema/cmake/schema.cmake new file mode 100644 index 00000000..cfe1e0e0 --- /dev/null +++ b/buildcc/schema/cmake/schema.cmake @@ -0,0 +1,113 @@ +# schema test +if (${TESTING}) + add_library(mock_schema STATIC + include/schema/interface/serialization_interface.h + + include/schema/path.h + + src/custom_generator_serialization.cpp + include/schema/custom_generator_schema.h + include/schema/custom_generator_serialization.h + + src/target_serialization.cpp + include/schema/target_schema.h + include/schema/target_serialization.h + ) + target_include_directories(mock_schema PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/mock/include + ${SCHEMA_BUILD_DIR} + ) + target_link_libraries(mock_schema PUBLIC + mock_env + nlohmann_json::nlohmann_json + + CppUTest + CppUTestExt + ${TEST_LINK_LIBS} + ) + + target_compile_options(mock_schema PUBLIC ${TEST_COMPILE_FLAGS} ${BUILD_COMPILE_FLAGS}) + target_link_options(mock_schema PUBLIC ${TEST_LINK_FLAGS} ${BUILD_LINK_FLAGS}) + + # Tests + add_executable(test_path_schema + test/test_path_schema.cpp + ) + target_link_libraries(test_path_schema PRIVATE mock_schema) + + add_executable(test_custom_generator_serialization + test/test_custom_generator_serialization.cpp + ) + target_link_libraries(test_custom_generator_serialization PRIVATE mock_schema) + + add_executable(test_target_serialization + test/test_target_serialization.cpp + ) + target_link_libraries(test_target_serialization PRIVATE mock_schema) + + add_test(NAME test_path_schema COMMAND test_path_schema + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test + ) + add_test(NAME test_custom_generator_serialization COMMAND test_custom_generator_serialization + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test + ) + add_test(NAME test_target_serialization COMMAND test_target_serialization + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test + ) +endif() + +set(SCHEMA_SRCS + include/schema/interface/serialization_interface.h + + include/schema/path.h + + src/custom_generator_serialization.cpp + include/schema/custom_generator_schema.h + include/schema/custom_generator_serialization.h + + src/target_serialization.cpp + include/schema/target_schema.h + include/schema/target_serialization.h +) + +if(${BUILDCC_BUILD_AS_SINGLE_LIB}) + target_sources(buildcc PRIVATE + ${SCHEMA_SRCS} + ) + target_include_directories(buildcc PUBLIC + $ + $ + ) + target_include_directories(buildcc PRIVATE + ${SCHEMA_BUILD_DIR} + ) +endif() + +if(${BUILDCC_BUILD_AS_INTERFACE}) + m_clangtidy("schema") + add_library(schema + ${SCHEMA_SRCS} + ) + target_include_directories(schema PUBLIC + $ + $ + ) + target_link_libraries(schema PUBLIC + env + nlohmann_json::nlohmann_json + ) + target_include_directories(schema PRIVATE + ${SCHEMA_BUILD_DIR} + ) + target_compile_options(schema PRIVATE ${BUILD_COMPILE_FLAGS}) + target_link_options(schema PRIVATE ${BUILD_LINK_FLAGS}) +endif() + +if (${BUILDCC_INSTALL}) + if (${BUILDCC_BUILD_AS_INTERFACE}) + install(TARGETS schema DESTINATION lib EXPORT schemaConfig) + install(EXPORT schemaConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/schema") + endif() + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}") +endif() diff --git a/buildcc/schema/include/schema/custom_generator_schema.h b/buildcc/schema/include/schema/custom_generator_schema.h new file mode 100644 index 00000000..a4088a66 --- /dev/null +++ b/buildcc/schema/include/schema/custom_generator_schema.h @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCHEMA_CUSTOM_GENERATOR_SCHEMA_H_ +#define SCHEMA_CUSTOM_GENERATOR_SCHEMA_H_ + +#include + +#include "schema/path.h" + +namespace buildcc::internal { + +struct CustomGeneratorSchema { +private: + static constexpr const char *const kName = "name"; + static constexpr const char *const kIds = "ids"; + +public: + using IdKey = std::string; + struct IdInfo { + private: + static constexpr const char *const kInputs = "inputs"; + static constexpr const char *const kOutputs = "outputs"; + static constexpr const char *const kUserblob = "userblob"; + + public: + PathInfoList inputs; + PathList outputs; + std::vector userblob; + + friend void to_json(json &j, const IdInfo &info) { + j[kInputs] = info.inputs; + j[kOutputs] = info.outputs; + j[kUserblob] = info.userblob; + } + + friend void from_json(const json &j, IdInfo &info) { + j.at(kInputs).get_to(info.inputs); + j.at(kOutputs).get_to(info.outputs); + j.at(kUserblob).get_to(info.userblob); + } + }; + + using IdPair = std::pair; + + std::string name; + std::unordered_map internal_ids; + + friend void to_json(json &j, const CustomGeneratorSchema &schema) { + j[kName] = schema.name; + j[kIds] = schema.internal_ids; + } + + friend void from_json(const json &j, CustomGeneratorSchema &schema) { + j.at(kName).get_to(schema.name); + j.at(kIds).get_to(schema.internal_ids); + } +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/schema/include/schema/custom_generator_serialization.h b/buildcc/schema/include/schema/custom_generator_serialization.h new file mode 100644 index 00000000..45b03a1f --- /dev/null +++ b/buildcc/schema/include/schema/custom_generator_serialization.h @@ -0,0 +1,52 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCHEMA_CUSTOM_GENERATOR_SERIALIZATION_H_ +#define SCHEMA_CUSTOM_GENERATOR_SERIALIZATION_H_ + +#include +#include +#include + +#include "schema/interface/serialization_interface.h" + +#include "schema/custom_generator_schema.h" +#include "schema/path.h" + +namespace buildcc::internal { + +class CustomGeneratorSerialization : public SerializationInterface { +public: + explicit CustomGeneratorSerialization(const fs::path &serialized_file) + : SerializationInterface(serialized_file) {} + + void UpdateStore(const CustomGeneratorSchema &store) { store_ = store; } + const CustomGeneratorSchema &GetLoad() const { return load_; } + const CustomGeneratorSchema &GetStore() const { return store_; } + +private: + bool Verify(const std::string &serialized_data) override; + bool Load(const std::string &serialized_data) override; + bool Store(const fs::path &absolute_serialized_file) override; + +private: + CustomGeneratorSchema load_; + CustomGeneratorSchema store_; +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/schema/include/schema/interface/serialization_interface.h b/buildcc/schema/include/schema/interface/serialization_interface.h new file mode 100644 index 00000000..c09bdde2 --- /dev/null +++ b/buildcc/schema/include/schema/interface/serialization_interface.h @@ -0,0 +1,76 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCHEMA_INTERFACE_SERIALIZATION_INTERFACE_H_ +#define SCHEMA_INTERFACE_SERIALIZATION_INTERFACE_H_ + +#include + +#include "env/assert_fatal.h" +#include "env/util.h" + +#include "schema/path.h" + +namespace fs = std::filesystem; + +namespace buildcc::internal { + +class SerializationInterface { +public: + explicit SerializationInterface(const fs::path &serialized_file) + : serialized_file_(serialized_file) {} + virtual ~SerializationInterface() = default; + + bool LoadFromFile() { + std::string buffer; + + // Read from serialized file + bool is_loaded = + env::load_file(path_as_string(serialized_file_).c_str(), true, &buffer); + if (!is_loaded) { + return false; + } + + // Verify serialized data as per schema + if (!Verify(buffer)) { + return false; + } + + // Load serialized data as C++ data + loaded_ = Load(buffer); + return loaded_; + } + + bool StoreToFile() { return Store(serialized_file_); } + + const fs::path &GetSerializedFile() const noexcept { + return serialized_file_; + } + bool IsLoaded() const noexcept { return loaded_; } + +private: + virtual bool Verify(const std::string &serialized_data) = 0; + virtual bool Load(const std::string &serialized_data) = 0; + virtual bool Store(const fs::path &absolute_serialized_file) = 0; + +private: + fs::path serialized_file_; + bool loaded_{false}; +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/schema/include/schema/path.h b/buildcc/schema/include/schema/path.h new file mode 100644 index 00000000..0c07863f --- /dev/null +++ b/buildcc/schema/include/schema/path.h @@ -0,0 +1,260 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCHEMA_PATH_H_ +#define SCHEMA_PATH_H_ + +#include +#include +#include +#include + +// Env +#include "env/assert_fatal.h" + +// Third party +#include "fmt/format.h" +#include "nlohmann/json.hpp" + +namespace fs = std::filesystem; +using json = nlohmann::ordered_json; + +namespace buildcc::internal { + +struct PathInfo { +private: + static constexpr const char *const kPath = "path"; + static constexpr const char *const kHash = "hash"; + +public: + PathInfo() = default; + PathInfo(const std::string &p, const std::string &h) : path(p), hash(h) {} + + bool operator==(const PathInfo &other) const { + return ((path == other.path) && (hash == other.hash)); + } + + /** + * @brief Sanitizes a fs::path or std::string to a standard path string + * - Converts backslash (\) to forward slash (/) + * - Makes fs::lexically_normal (see std::filesystem library impl) + * + * @param str User provided fs::path/std::string + * @return std::string Sanitized path as std::string + */ + static std::string ToPathString(const fs::path &p) { + return fs::path(p).make_preferred().lexically_normal().string(); + } + + /** + * @brief Formats a fs::path or std::string for display + * - All the sanitization as done in `ToPathString` + * Additionally + * - Adds quotation marks ("") when a space is detected + * For example: test/hello world/ -> "test/hello world/" + * + * NOTE: Use this API only in places where you would like to output to + * console/run or create command through subprocess + * + * @param str User provided fs::path/std::string + * @return std::string Sanitized path as std::string for display + */ + static std::string ToPathDisplayString(const fs::path &p) { + auto path_str = ToPathString(p); + // if spaces are present in the path string, surround this with brackets + if (path_str.find(' ') != std::string::npos) { + path_str = fmt::format("\"{}\"", path_str); + } + return path_str; + } + + friend void to_json(json &j, const PathInfo &info) { + j[kPath] = info.path; + j[kHash] = info.hash; + } + + friend void from_json(const json &j, PathInfo &info) { + j.at(kPath).get_to(info.path); + j.at(kHash).get_to(info.hash); + } + + std::string path; + std::string hash; +}; + +/** + * @brief Stores path + */ +class PathList { +public: + PathList() = default; + PathList(std::initializer_list paths) { + for (const auto &path : paths) { + Emplace(path); + } + } + + void Emplace(const fs::path &p) { + auto path_str = PathInfo::ToPathString(p); + paths_.emplace_back(std::move(path_str)); + } + + // TODO, Create a move version of Emplace(std::string &&pstr) + + void Insert(const PathList &other) { + paths_.insert(paths_.end(), other.paths_.begin(), other.paths_.end()); + } + + // TODO, Create a move version of Insert (PathList &&) + + // TODO, Remove this (redundant, use operator == overload instead) + bool IsEqual(const PathList &other) const { return paths_ == other.paths_; } + + const std::vector &GetPaths() const { return paths_; } + + std::unordered_set GetUnorderedPaths() const { + std::unordered_set unordered_paths(paths_.begin(), + paths_.end()); + return unordered_paths; + } + + bool operator==(const PathList &other) const { return IsEqual(other); } + + friend void to_json(json &j, const PathList &plist) { j = plist.paths_; } + + friend void from_json(const json &j, PathList &plist) { + j.get_to(plist.paths_); + } + +private: + std::vector paths_; +}; + +/** + * @brief Stores path + path hash in a hashmap + * + */ +class PathInfoList { +public: + PathInfoList() = default; + explicit PathInfoList( + std::initializer_list> + path_infos) { + for (const auto &pinfo : path_infos) { + Emplace(pinfo.first, pinfo.second); + } + } + + void Emplace(const fs::path &p, const std::string &hash) { + auto path_str = PathInfo::ToPathString(p); + infos_.emplace_back(PathInfo(path_str, hash)); + } + + // TODO, Create a move version of Emplace(std::string &&pstr, std::string + // &&hash) + + void Insert(const PathInfoList &other) { + infos_.insert(infos_.end(), other.infos_.begin(), other.infos_.end()); + } + + // TODO, Create a move version of Insert(PathInfoList &&other) + + void ComputeHashForAll() { + for (auto &info : infos_) { + info.hash = ComputeHash(info.path); + } + } + + // TODO, Remove redundant function (use operator == overload) + bool IsEqual(const PathInfoList &other) const { + return infos_ == other.infos_; + } + + const std::vector &GetPathInfos() const { return infos_; } + + std::unordered_map GetUnorderedPathInfos() const { + std::unordered_map unordered_path_infos; + for (const auto &info : infos_) { + unordered_path_infos.try_emplace(info.path, info.hash); + } + return unordered_path_infos; + } + + std::vector GetPaths() const { + std::vector paths; + for (const auto &info : infos_) { + paths.emplace_back(info.path); + } + return paths; + } + + // TODO, Add Compute Strategy enum + static std::string ComputeHash(const std::string &pstr) { + auto path_str = PathInfo::ToPathString(pstr); + + // TODO, There might be a file checksum hash compute strategy + // This is the timestamp hash compute strategy + std::error_code errcode; + const std::uint64_t last_write_timestamp = + std::filesystem::last_write_time(path_str, errcode) + .time_since_epoch() + .count(); + env::assert_fatal(errcode.value() == 0, + fmt::format("{} not found", path_str)); + return std::to_string(last_write_timestamp); + } + + bool operator==(const PathInfoList &other) const { return IsEqual(other); } + + friend void to_json(json &j, const PathInfoList &plist) { j = plist.infos_; } + + friend void from_json(const json &j, PathInfoList &plist) { + j.get_to(plist.infos_); + } + +private: + std::vector infos_; +}; + +} // namespace buildcc::internal + +namespace buildcc { + +inline std::string path_as_string(const fs::path &p) { + return internal::PathInfo::ToPathString(p); +} + +inline std::string path_as_display_string(const fs::path &p) { + return internal::PathInfo::ToPathDisplayString(p); +} + +inline fs::path string_as_path(const std::string &p) { + return path_as_string(p); +} + +} // namespace buildcc + +// FMT specialization + +template <> struct fmt::formatter : formatter { + template + auto format(const fs::path &p, FormatContext &ctx) { + return formatter::format(buildcc::path_as_display_string(p), + ctx); + } +}; + +#endif diff --git a/buildcc/schema/include/schema/target_schema.h b/buildcc/schema/include/schema/target_schema.h new file mode 100644 index 00000000..474bcb86 --- /dev/null +++ b/buildcc/schema/include/schema/target_schema.h @@ -0,0 +1,144 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCHEMA_TARGET_SCHEMA_H_ +#define SCHEMA_TARGET_SCHEMA_H_ + +#include +#include + +#include "schema/path.h" +#include "schema/target_type.h" + +namespace buildcc::internal { + +struct TargetSchema { + std::string name; + TargetType type{TargetType::Undefined}; + + PathInfoList sources; + PathInfoList headers; + PathInfoList pchs; + + PathInfoList libs; + std::vector external_libs; + + PathList include_dirs; + PathList lib_dirs; + + std::vector preprocessor_flags; + std::vector common_compile_flags; + std::vector pch_compile_flags; + std::vector pch_object_flags; + std::vector asm_compile_flags; + std::vector c_compile_flags; + std::vector cpp_compile_flags; + std::vector link_flags; + + PathInfoList compile_dependencies; + PathInfoList link_dependencies; + + // TODO, Verify this using fs::exists + bool pch_compiled{false}; + bool target_linked{false}; + +private: + static constexpr const char *const kName = "name"; + static constexpr const char *const kType = "type"; + + static constexpr const char *const kSources = "sources"; + static constexpr const char *const kHeaders = "headers"; + static constexpr const char *const kPchs = "pchs"; + static constexpr const char *const kLibs = "libs"; + static constexpr const char *const kExternalLibs = "external_libs"; + + static constexpr const char *const kIncludeDirs = "include_dirs"; + static constexpr const char *const kLibDirs = "lib_dirs"; + + static constexpr const char *const kPreprocessorFlags = "preprocessor_flags"; + static constexpr const char *const kCommonCompileFlags = + "common_compile_flags"; + static constexpr const char *const kPchCompileFlags = "pch_compile_flags"; + static constexpr const char *const kPchObjectFlags = "pch_object_flags"; + static constexpr const char *const kAsmCompileFlags = "asm_compile_flags"; + static constexpr const char *const kCCompileFlags = "c_compile_flags"; + static constexpr const char *const kCppCompileFlags = "cpp_compile_flags"; + static constexpr const char *const kLinkFlags = "link_flags"; + + static constexpr const char *const kCompileDependencies = + "compile_dependencies"; + static constexpr const char *const kLinkDependencies = "link_dependencies"; + + static constexpr const char *const kPchCompiled = "pch_compiled"; + static constexpr const char *const kTargetLinked = "target_linked"; + +public: + friend void to_json(json &j, const TargetSchema &schema) { + j[kName] = schema.name; + j[kType] = schema.type; + j[kSources] = schema.sources; + j[kHeaders] = schema.headers; + j[kPchs] = schema.pchs; + j[kLibs] = schema.libs; + j[kExternalLibs] = schema.external_libs; + j[kIncludeDirs] = schema.include_dirs; + j[kLibDirs] = schema.lib_dirs; + + j[kPreprocessorFlags] = schema.preprocessor_flags; + j[kCommonCompileFlags] = schema.common_compile_flags; + j[kPchCompileFlags] = schema.pch_compile_flags; + j[kPchObjectFlags] = schema.pch_object_flags; + j[kAsmCompileFlags] = schema.asm_compile_flags; + j[kCCompileFlags] = schema.c_compile_flags; + j[kCppCompileFlags] = schema.cpp_compile_flags; + j[kLinkFlags] = schema.link_flags; + + j[kCompileDependencies] = schema.compile_dependencies; + j[kLinkDependencies] = schema.link_dependencies; + j[kPchCompiled] = schema.pch_compiled; + j[kTargetLinked] = schema.target_linked; + } + + friend void from_json(const json &j, TargetSchema &schema) { + j.at(kName).get_to(schema.name); + j.at(kType).get_to(schema.type); + j.at(kSources).get_to(schema.sources); + j.at(kHeaders).get_to(schema.headers); + j.at(kPchs).get_to(schema.pchs); + j.at(kLibs).get_to(schema.libs); + j.at(kExternalLibs).get_to(schema.external_libs); + j.at(kIncludeDirs).get_to(schema.include_dirs); + j.at(kLibDirs).get_to(schema.lib_dirs); + + j.at(kPreprocessorFlags).get_to(schema.preprocessor_flags); + j.at(kCommonCompileFlags).get_to(schema.common_compile_flags); + j.at(kPchCompileFlags).get_to(schema.pch_compile_flags); + j.at(kPchObjectFlags).get_to(schema.pch_object_flags); + j.at(kAsmCompileFlags).get_to(schema.asm_compile_flags); + j.at(kCCompileFlags).get_to(schema.c_compile_flags); + j.at(kCppCompileFlags).get_to(schema.cpp_compile_flags); + j.at(kLinkFlags).get_to(schema.link_flags); + + j.at(kCompileDependencies).get_to(schema.compile_dependencies); + j.at(kLinkDependencies).get_to(schema.link_dependencies); + j.at(kPchCompiled).get_to(schema.pch_compiled); + j.at(kTargetLinked).get_to(schema.target_linked); + } +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/schema/include/schema/target_serialization.h b/buildcc/schema/include/schema/target_serialization.h new file mode 100644 index 00000000..f8cc532b --- /dev/null +++ b/buildcc/schema/include/schema/target_serialization.h @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCHEMA_TARGET_SERIALIZATION_H_ +#define SCHEMA_TARGET_SERIALIZATION_H_ + +#include + +#include "schema/path.h" +#include "schema/target_schema.h" + +#include "schema/interface/serialization_interface.h" + +namespace buildcc::internal { + +class TargetSerialization : public SerializationInterface { +public: + TargetSerialization(const fs::path &serialized_file) + : SerializationInterface(serialized_file) {} + + void UpdatePchCompiled(const TargetSchema &store); + void UpdateTargetCompiled(); + void AddSource(const std::string &source, const std::string &hash); + void UpdateStore(const TargetSchema &store); + + const TargetSchema &GetLoad() const { return load_; } + const TargetSchema &GetStore() const { return store_; } + +private: + bool Verify(const std::string &serialized_data) override; + bool Load(const std::string &serialized_data) override; + bool Store(const fs::path &absolute_serialized_file) override; + +private: + TargetSchema load_; + TargetSchema store_; + + std::mutex add_source_mutex; +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/schema/include/schema/target_type.h b/buildcc/schema/include/schema/target_type.h new file mode 100644 index 00000000..2871551a --- /dev/null +++ b/buildcc/schema/include/schema/target_type.h @@ -0,0 +1,66 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCHEMA_TARGET_TYPE_H_ +#define SCHEMA_TARGET_TYPE_H_ + +#include +#include +#include + +namespace buildcc { + +enum class TargetType { + Executable, ///< Executable Target type + StaticLibrary, ///< Static library target type + DynamicLibrary, ///< Dynamic library target type + Undefined, ///< Undefined target type +}; + +constexpr std::array, 3> + kTargetTypeInfo{ + std::make_pair("executable", TargetType::Executable), + std::make_pair("static_library", TargetType::StaticLibrary), + std::make_pair("dynamic_library", TargetType::DynamicLibrary), + }; + +template void to_json(JsonType &j, TargetType type) { + j = nullptr; + auto iter = std::find_if(kTargetTypeInfo.cbegin(), kTargetTypeInfo.cend(), + [type](const auto &p) { return p.second == type; }); + if (iter != kTargetTypeInfo.cend()) { + j = iter->first; + } +} + +template +void from_json(const JsonType &j, TargetType &type) { + type = TargetType::Undefined; + if (j.is_string()) { + std::string name; + j.get_to(name); + auto iter = + std::find_if(kTargetTypeInfo.cbegin(), kTargetTypeInfo.cend(), + [&name](const auto &p) { return p.first == name; }); + if (iter != kTargetTypeInfo.cend()) { + type = iter->second; + } + } +} + +} // namespace buildcc + +#endif diff --git a/buildcc/schema/src/custom_generator_serialization.cpp b/buildcc/schema/src/custom_generator_serialization.cpp new file mode 100644 index 00000000..e71d0097 --- /dev/null +++ b/buildcc/schema/src/custom_generator_serialization.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "schema/custom_generator_serialization.h" + +// Third party +#include "nlohmann/json.hpp" + +namespace buildcc::internal { + +// PRIVATE + +bool CustomGeneratorSerialization::Verify(const std::string &serialized_data) { + (void)serialized_data; + return true; +} + +bool CustomGeneratorSerialization::Load(const std::string &serialized_data) { + json j = json::parse(serialized_data, nullptr, false); + bool loaded = !j.is_discarded(); + + if (loaded) { + try { + load_ = j.get(); + } catch (const std::exception &e) { + env::log_critical(__FUNCTION__, e.what()); + loaded = false; + } + } + return loaded; +} + +bool CustomGeneratorSerialization::Store( + const fs::path &absolute_serialized_file) { + json j = store_; + auto data = j.dump(4); + return env::save_file(path_as_string(absolute_serialized_file).c_str(), data, + false); +} + +} // namespace buildcc::internal diff --git a/buildcc/schema/src/target_serialization.cpp b/buildcc/schema/src/target_serialization.cpp new file mode 100644 index 00000000..55826069 --- /dev/null +++ b/buildcc/schema/src/target_serialization.cpp @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "schema/target_serialization.h" + +namespace buildcc::internal { + +// PUBLIC +void TargetSerialization::UpdatePchCompiled(const TargetSchema &store) { + store_.pchs = store.pchs; + store_.pch_compiled = true; +} + +// TODO, When you add source here you need to add it with the hash +void TargetSerialization::AddSource(const std::string &source, + const std::string &hash) { + std::scoped_lock guard(add_source_mutex); + store_.sources.Emplace(source, hash); +} + +void TargetSerialization::UpdateTargetCompiled() { + store_.target_linked = true; +} + +void TargetSerialization::UpdateStore(const TargetSchema &store) { + TargetSchema temp = store; + temp.pchs = store_.pchs; + temp.pch_compiled = store_.pch_compiled; + temp.sources = store_.sources; + temp.target_linked = store_.target_linked; + store_ = std::move(temp); +} + +// PRIVATE +bool TargetSerialization::Verify(const std::string &serialized_data) { + (void)serialized_data; + return true; +} + +bool TargetSerialization::Load(const std::string &serialized_data) { + json j = json::parse(serialized_data, nullptr, false); + bool loaded = !j.is_discarded(); + + if (loaded) { + try { + load_ = j.get(); + } catch (const std::exception &e) { + env::log_critical(__FUNCTION__, e.what()); + loaded = false; + } + } + return loaded; +} + +bool TargetSerialization::Store(const fs::path &absolute_serialized_file) { + json j = store_; + auto data = j.dump(4); + return env::save_file(path_as_string(absolute_serialized_file).c_str(), data, + false); +} + +} // namespace buildcc::internal diff --git a/buildcc/schema/test/.gitignore b/buildcc/schema/test/.gitignore new file mode 100644 index 00000000..7628df5e --- /dev/null +++ b/buildcc/schema/test/.gitignore @@ -0,0 +1,2 @@ +*.bin +*.json diff --git a/buildcc/schema/test/dump/.gitkeep b/buildcc/schema/test/dump/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/schema/test/test_custom_generator_serialization.cpp b/buildcc/schema/test/test_custom_generator_serialization.cpp new file mode 100644 index 00000000..e7411470 --- /dev/null +++ b/buildcc/schema/test/test_custom_generator_serialization.cpp @@ -0,0 +1,70 @@ +#include "schema/custom_generator_serialization.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(CustomGeneratorSerializationTestGroup) +{ + void teardown() { + mock().clear(); + } +}; +// clang-format on + +TEST(CustomGeneratorSerializationTestGroup, JsonParse_Failure) { + { + // JSON Parse fails + buildcc::internal::CustomGeneratorSerialization serialization( + "dump/CustomGeneratorJsonParseFailure.json"); + + buildcc::env::save_file(serialization.GetSerializedFile().string().c_str(), + std::string(""), false); + bool loaded = serialization.LoadFromFile(); + CHECK_FALSE(loaded); + } + + { + // Custom Generator Schema conversion fails + buildcc::internal::CustomGeneratorSerialization serialization( + "dump/CustomGeneratorJsonParseFailure.json"); + + auto data = R"({"name": ""})"; + buildcc::env::save_file(serialization.GetSerializedFile().string().c_str(), + data, false); + bool loaded = serialization.LoadFromFile(); + CHECK_FALSE(loaded); + } +} + +TEST(CustomGeneratorSerializationTestGroup, FormatEmptyCheck) { + buildcc::internal::CustomGeneratorSerialization serialization( + "dump/CustomGeneratorFormatEmptyCheck.json"); + + bool stored = serialization.StoreToFile(); + CHECK_TRUE(stored); +} + +TEST(CustomGeneratorSerializationTestGroup, EmptyFile_Failure) { + { + buildcc::internal::CustomGeneratorSerialization serialization( + "dump/CustomGeneratorEmptyFile.json"); + CHECK_FALSE(serialization.LoadFromFile()); + } + + { + buildcc::internal::CustomGeneratorSerialization serialization( + "dump/CustomGeneratorEmptyFile.json"); + buildcc::env::save_file(serialization.GetSerializedFile().string().c_str(), + "", false); + CHECK_FALSE(serialization.LoadFromFile()); + } +} + +int main(int ac, char **av) { + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/schema/test/test_path_schema.cpp b/buildcc/schema/test/test_path_schema.cpp new file mode 100644 index 00000000..fe5543b2 --- /dev/null +++ b/buildcc/schema/test/test_path_schema.cpp @@ -0,0 +1,147 @@ +// Internal +#include "schema/path.h" + +#include "env/host_os.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" + +// clang-format off +TEST_GROUP(PathSchemaTestGroup) +{ +}; +// clang-format on + +TEST(PathSchemaTestGroup, PathList) { buildcc::internal::PathList paths; } + +TEST(PathSchemaTestGroup, Path_ToPathString) { + auto path_str = + buildcc::internal::PathInfo::ToPathString("hello/\\first.txt"); + + if constexpr (buildcc::env::is_win()) { + STRCMP_EQUAL("hello\\first.txt", path_str.c_str()); + } else if constexpr (buildcc::env::is_linux() || buildcc::env::is_mac() || + buildcc::env::is_unix()) { + STRCMP_EQUAL("hello/\\first.txt", path_str.c_str()); + } else { + FAIL("Operating system not supported"); + } +} + +TEST(PathSchemaTestGroup, Path_ToPathDisplayString) { + auto path_str = buildcc::internal::PathInfo::ToPathDisplayString( + "hello/\\first/hello world.txt"); + + if constexpr (buildcc::env::is_win()) { + STRCMP_EQUAL("\"hello\\first\\hello world.txt\"", path_str.c_str()); + } else if constexpr (buildcc::env::is_linux() || buildcc::env::is_mac() || + buildcc::env::is_unix()) { + STRCMP_EQUAL("\"hello/\\first/hello world.txt\"", path_str.c_str()); + } else { + FAIL("Operating system not supported"); + } +} + +TEST(PathSchemaTestGroup, PathInfoList_OrderedEmplace) { + buildcc::internal::PathInfoList path_infos; + + path_infos.Emplace("hello/world/first_file.txt", ""); + path_infos.Emplace("hello/world/second_file.txt", ""); + path_infos.Emplace("hello/world/third_file.txt", ""); + path_infos.Emplace("hello/world/fourth_file.txt", ""); + + std::vector paths; + if constexpr (buildcc::env::is_win()) { + paths = { + "hello\\world\\first_file.txt", + "hello\\world\\second_file.txt", + "hello\\world\\third_file.txt", + "hello\\world\\fourth_file.txt", + }; + } else if constexpr (buildcc::env::is_linux() || buildcc::env::is_unix() || + buildcc::env::is_mac()) { + paths = { + "hello/world/first_file.txt", + "hello/world/second_file.txt", + "hello/world/third_file.txt", + "hello/world/fourth_file.txt", + }; + } else { + FAIL("Operating system not supported"); + } + + auto inserted_paths = path_infos.GetPaths(); + for (std::size_t i = 0; i < inserted_paths.size(); i++) { + STRCMP_EQUAL(paths[i].c_str(), inserted_paths[i].c_str()); + } + + json j = path_infos; + UT_PRINT(j.dump().c_str()); +} + +TEST(PathSchemaTestGroup, PathInfoList_GetChanged) { + { + buildcc::internal::PathInfoList pinfolist1{ + {"first.txt", "1"}, + {"second.txt", "2"}, + {"third.txt", "3"}, + }; + buildcc::internal::PathInfoList pinfolist2{ + {"first.txt", "1"}, + {"second.txt", "2"}, + {"third.txt", "3"}, + }; + CHECK_TRUE(pinfolist1.IsEqual(pinfolist2)); + } + + // Missing path info + { + buildcc::internal::PathInfoList pinfolist1{ + {"first.txt", "1"}, + {"second.txt", "2"}, + {"third.txt", "3"}, + }; + buildcc::internal::PathInfoList pinfolist2{ + {"first.txt", "1"}, {"second.txt", "2"}, + // {"third.txt", "3"}, + }; + CHECK_FALSE(pinfolist1.IsEqual(pinfolist2)); + } + + // Wrong hash + { + buildcc::internal::PathInfoList pinfolist1{ + {"first.txt", "1"}, + {"second.txt", "2"}, + {"third.txt", "3"}, + }; + buildcc::internal::PathInfoList pinfolist2{ + {"first.txt", "1"}, + {"second.txt", "2"}, + {"third.txt", "4"}, + }; + CHECK_FALSE(pinfolist1.IsEqual(pinfolist2)); + } + + // Wrong order + { + buildcc::internal::PathInfoList pinfolist1{ + {"first.txt", "1"}, + {"second.txt", "2"}, + {"third.txt", "3"}, + }; + buildcc::internal::PathInfoList pinfolist2{ + {"first.txt", "1"}, + {"third.txt", "3"}, + {"second.txt", "2"}, + }; + CHECK_FALSE(pinfolist1.IsEqual(pinfolist2)); + } +} + +int main(int ac, char **av) { + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/schema/test/test_target_serialization.cpp b/buildcc/schema/test/test_target_serialization.cpp new file mode 100644 index 00000000..522a6b64 --- /dev/null +++ b/buildcc/schema/test/test_target_serialization.cpp @@ -0,0 +1,196 @@ +#include "schema/target_serialization.h" + +#include "nlohmann/json.hpp" + +using json = nlohmann::ordered_json; + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(TargetSerializationTestGroup) +{ + void teardown() { + mock().clear(); + } +}; +// clang-format on + +TEST(TargetSerializationTestGroup, TargetType) { + buildcc::TargetType type; + json j; + + { + j = buildcc::kTargetTypeInfo[0].first; + from_json(j, type); + CHECK_TRUE(type == buildcc::kTargetTypeInfo[0].second); + } + + { + j = buildcc::kTargetTypeInfo[1].first; + from_json(j, type); + CHECK_TRUE(type == buildcc::kTargetTypeInfo[1].second); + } + + { + j = buildcc::kTargetTypeInfo[2].first; + from_json(j, type); + CHECK_TRUE(type == buildcc::kTargetTypeInfo[2].second); + } + + { + j = "should_not_exist"; + from_json(j, type); + CHECK_TRUE(type == buildcc::TargetType::Undefined); + } + + { + j = nullptr; + from_json(j, type); + CHECK_TRUE(type == buildcc::TargetType::Undefined); + } +} + +TEST(TargetSerializationTestGroup, TargetSerialization_TargetType) { + { + // Target Type executable + buildcc::internal::TargetSerialization serialization( + "dump/TargetTypeTest.json"); + + buildcc::internal::TargetSchema schema; + schema.type = buildcc::TargetType::Executable; + serialization.UpdateStore(schema); + bool store = serialization.StoreToFile(); + CHECK_TRUE(store); + + bool load = serialization.LoadFromFile(); + CHECK_TRUE(load); + CHECK_TRUE(serialization.GetLoad().type == buildcc::TargetType::Executable); + } + + { + // Target Type static library + buildcc::internal::TargetSerialization serialization( + "dump/TargetTypeTest.json"); + + buildcc::internal::TargetSchema schema; + schema.type = buildcc::TargetType::StaticLibrary; + serialization.UpdateStore(schema); + bool store = serialization.StoreToFile(); + CHECK_TRUE(store); + + bool load = serialization.LoadFromFile(); + CHECK_TRUE(load); + CHECK_TRUE(serialization.GetLoad().type == + buildcc::TargetType::StaticLibrary); + } + + { + // Target Type dynamic library + buildcc::internal::TargetSerialization serialization( + "dump/TargetTypeTest.json"); + + buildcc::internal::TargetSchema schema; + schema.type = buildcc::TargetType::DynamicLibrary; + serialization.UpdateStore(schema); + bool store = serialization.StoreToFile(); + CHECK_TRUE(store); + + bool load = serialization.LoadFromFile(); + CHECK_TRUE(load); + CHECK_TRUE(serialization.GetLoad().type == + buildcc::TargetType::DynamicLibrary); + } + + { + // Target Type undefined + buildcc::internal::TargetSerialization serialization( + "dump/TargetTypeTest.json"); + + buildcc::internal::TargetSchema schema; + schema.type = buildcc::TargetType::Undefined; + serialization.UpdateStore(schema); + bool store = serialization.StoreToFile(); + CHECK_TRUE(store); + + bool load = serialization.LoadFromFile(); + CHECK_TRUE(load); + CHECK_TRUE(serialization.GetLoad().type == buildcc::TargetType::Undefined); + } + + { + // Target Type random value + buildcc::internal::TargetSerialization serialization( + "dump/TargetTypeTest.json"); + + buildcc::internal::TargetSchema schema; + schema.type = (buildcc::TargetType)65535; + serialization.UpdateStore(schema); + bool store = serialization.StoreToFile(); + CHECK_TRUE(store); + + bool load = serialization.LoadFromFile(); + CHECK_TRUE(load); + CHECK_TRUE(serialization.GetLoad().type == buildcc::TargetType::Undefined); + } +} + +TEST(TargetSerializationTestGroup, JsonParse_Failure) { + { + // JSON Parse fails + buildcc::internal::TargetSerialization serialization( + "dump/TargetJsonParseFailure.json"); + + buildcc::env::save_file(serialization.GetSerializedFile().string().c_str(), + std::string(""), false); + bool loaded = serialization.LoadFromFile(); + CHECK_FALSE(loaded); + } + + { + // Custom Generator Schema conversion fails + buildcc::internal::TargetSerialization serialization( + "dump/TargetJsonParseFailure.json"); + + auto data = R"({"name": ""})"; + buildcc::env::save_file(serialization.GetSerializedFile().string().c_str(), + data, false); + bool loaded = serialization.LoadFromFile(); + CHECK_FALSE(loaded); + } +} + +TEST(TargetSerializationTestGroup, FormatEmptyCheck) { + buildcc::internal::TargetSerialization serialization( + "dump/TargetFormatEmptyCheck.json"); + + bool stored = serialization.StoreToFile(); + CHECK_TRUE(stored); + + bool loaded = serialization.LoadFromFile(); + CHECK_TRUE(loaded); +} + +TEST(TargetSerializationTestGroup, EmptyFile_Failure) { + { + buildcc::internal::TargetSerialization serialization( + "dump/TargetEmptyFile.json"); + CHECK_FALSE(serialization.LoadFromFile()); + } + + { + buildcc::internal::TargetSerialization serialization( + "dump/TargetEmptyFile.json"); + buildcc::env::save_file(serialization.GetSerializedFile().string().c_str(), + "", false); + CHECK_FALSE(serialization.LoadFromFile()); + } +} + +int main(int ac, char **av) { + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/src/args.cpp b/buildcc/src/args.cpp deleted file mode 100644 index e192754a..00000000 --- a/buildcc/src/args.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "args.h" - -namespace buildcc { - -void Args::AddCustomToolchain(const std::string &name, - const std::string &description, - Toolchain &custom_toolchain_arg) { - AddToolchain(name, description, "Custom", custom_toolchain_arg); -} - -void Args::Parse(int argc, char **argv) { - try { - app_.parse(argc, argv); - } catch (const CLI::ParseError &e) { - env::log_critical("Args::Parse", e.what()); - exit(app_.exit(e)); - } -} - -// Private - -void Args::Initialize() { - RootArgs(); - ToolchainArgs(); -} - -void Args::RootArgs() { - app_.set_help_all_flag("--help-all", "Expand individual options"); - - // TODO, Currently only expects 1 - // From CLI11 2.0 onwards multiple configuration files can be added, increase - // this limit - app_.set_config("--config", "", "Read a .toml file")->expected(1); - - // Root flags - app_.add_flag("-c,--clean", clean_, "Clean artifacts")->group("Root"); - app_.add_option("-l,--loglevel", loglevel_, "LogLevel settings") - ->transform(CLI::CheckedTransformer(loglevel_map_, CLI::ignore_case)) - ->group("Root"); - - // Dir flags - app_.add_option("--root_dir", project_root_dir_, - "Project root directory (relative to current directory)") - ->required() - ->group("Root"); - app_.add_option("--build_dir", project_build_dir_, - "Project build dir (relative to current directory)") - ->required() - ->group("Root"); -} - -void Args::ToolchainArgs() { - toolchain_ = app_.add_subcommand("toolchain", "Select Toolchain"); - AddToolchain("gcc", "GNU GCC Toolchain", "Supported", gcc_toolchain_); - AddToolchain("msvc", "MSVC Toolchain", "Supported", msvc_toolchain_); -} - -void Args::AddToolchain(const std::string &name, const std::string &description, - const std::string &group, Toolchain &toolchain_arg) { - CLI::App *toolchain_spec = - toolchain_->add_subcommand(name, description)->group(group); - toolchain_spec->add_flag("-b,--build", toolchain_arg.build); - toolchain_spec->add_flag("-t,--test", toolchain_arg.test); -} - -} // namespace buildcc diff --git a/buildcc/src/register.cpp b/buildcc/src/register.cpp deleted file mode 100644 index 55906676..00000000 --- a/buildcc/src/register.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "register.h" - -#include - -#include "fmt/format.h" - -#include "assert_fatal.h" -#include "env.h" - -namespace fs = std::filesystem; - -namespace buildcc { - -void Register::Env() { - env::init(fs::current_path() / args_.GetProjectRootDir(), - fs::current_path() / args_.GetProjectBuildDir()); - env::set_log_level(args_.GetLogLevel()); -} - -void Register::Clean(std::function clean_cb) { - if (args_.Clean()) { - clean_cb(); - } -} - -void Register::Build(const Args::Toolchain &args_toolchain, - base::Target &target, - std::function build_cb) { - if (args_toolchain.build) { - tf::Task task = taskflow_.composed_of(target.GetTaskflow()).name("Task"); - deps_.insert({target.GetName(), task}); - build_cb(target); - } -} - -void Register::Test(const Args::Toolchain &args_toolchain, base::Target &target, - std::function test_cb) { - if (!(args_toolchain.build && args_toolchain.test)) { - return; - } - - const bool added = - tests_.emplace(target.GetName(), TestInfo(target, test_cb)).second; - env::assert_fatal( - added, fmt::format("Could not register test {}", target.GetName())); -} - -void Register::Dep(const base::Target &target, const base::Target &dependency) { - tf::Task target_task; - tf::Task dep_task; - try { - target_task = deps_.at(target.GetName()); - dep_task = deps_.at(dependency.GetName()); - target_task.succeed(dep_task); - } catch (const std::out_of_range &e) { - (void)e; - env::assert_fatal(false, "Call Register::Build API on target and " - "dependency before Register::Dep API"); - } -} - -void Register::RunBuild() { - executor_.run(taskflow_); - executor_.wait_for_all(); -} - -void Register::RunTest() { - for (const auto &t : tests_) { - env::log_info(__FUNCTION__, fmt::format("Testing \'{}\'", t.first)); - t.second.cb_(t.second.target_); - } -} - -} // namespace buildcc diff --git a/buildcc/targets/CMakeLists.txt b/buildcc/targets/CMakeLists.txt new file mode 100644 index 00000000..3067f675 --- /dev/null +++ b/buildcc/targets/CMakeLists.txt @@ -0,0 +1,43 @@ +set(TARGET_SPECIALIZED_SRCS + # Interfaces + include/targets/target_config_interface.h + + # Specialized targets + include/targets/target_gcc.h + include/targets/target_msvc.h + include/targets/target_mingw.h + + # User targets + include/targets/target_generic.h + include/targets/target_custom.h +) + +if(${BUILDCC_BUILD_AS_SINGLE_LIB}) + target_sources(buildcc PRIVATE + ${TARGET_SPECIALIZED_SRCS} + ) + target_include_directories(buildcc PUBLIC + $ + $ + ) +endif() + +m_clangtidy("target_specialized") +add_library(target_specialized INTERFACE + ${TARGET_SPECIALIZED_SRCS} +) +target_include_directories(target_specialized INTERFACE + $ + $ +) +target_link_libraries(target_specialized INTERFACE + target +) +if (${BUILDCC_INSTALL}) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}") + if (${BUILDCC_BUILD_AS_INTERFACE}) + # target_specialized Install + install(TARGETS target_specialized DESTINATION lib EXPORT target_specializedConfig) + install(EXPORT target_specializedConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/target_specialized") + endif() +endif() diff --git a/buildcc/targets/gcc/CMakeLists.txt b/buildcc/targets/gcc/CMakeLists.txt deleted file mode 100644 index 548a6069..00000000 --- a/buildcc/targets/gcc/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -# Target_gcc lib -m_clangtidy("target_gcc") -add_library(target_gcc - static_target_gcc.cpp - dynamic_target_gcc.cpp - executable_target_gcc.h - static_target_gcc.h - dynamic_target_gcc.h -) -target_include_directories(target_gcc PUBLIC - $ - $ -) -target_link_libraries(target_gcc PRIVATE - target -) -target_compile_options(target_gcc PRIVATE ${BUILD_COMPILE_FLAGS}) -target_link_options(target_gcc PRIVATE ${BUILD_LINK_FLAGS}) - -# TODO, Add Target_gcc tests - -# Target_gcc Install -if (${BUILDCC_INSTALL}) - install(TARGETS target_gcc DESTINATION lib EXPORT target_gccConfig) - install(FILES - ${CMAKE_CURRENT_SOURCE_DIR}/executable_target_gcc.h - ${CMAKE_CURRENT_SOURCE_DIR}/static_target_gcc.h - ${CMAKE_CURRENT_SOURCE_DIR}/dynamic_target_gcc.h - DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}" - ) - install(EXPORT target_gccConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/target_gcc") -endif() diff --git a/buildcc/targets/gcc/dynamic_target_gcc.cpp b/buildcc/targets/gcc/dynamic_target_gcc.cpp deleted file mode 100644 index 4d6fb1b0..00000000 --- a/buildcc/targets/gcc/dynamic_target_gcc.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "dynamic_target_gcc.h" - -#include "internal/util.h" - -// Env -#include "assert_fatal.h" - -namespace buildcc { - -// Compiling -std::vector DynamicTarget_gcc::CompileCommand( - const std::string &input_source, const std::string &output_source, - const std::string &compiler, - const std::string &aggregated_preprocessor_flags, - const std::string &aggregated_compile_flags, - const std::string &aggregated_include_dirs) const { - return { - compiler, - aggregated_preprocessor_flags, - aggregated_include_dirs, - aggregated_compile_flags, - "-fpic", - "-o", - output_source, - "-c", - input_source, - }; -} - -// Linking -std::vector -DynamicTarget_gcc::Link(const std::string &output_target, - const std::string &aggregated_link_flags, - const std::string &aggregated_compiled_sources, - const std::string &aggregated_lib_dirs, - const std::string &aggregated_lib_deps) const { - (void)aggregated_lib_dirs; - (void)aggregated_lib_deps; - // clang-format off - return { - GetToolchain().GetCppCompiler(), - "-shared", - aggregated_link_flags, - aggregated_compiled_sources, - "-o", - output_target, - }; -} - -} // namespace buildcc diff --git a/buildcc/targets/gcc/dynamic_target_gcc.h b/buildcc/targets/gcc/dynamic_target_gcc.h deleted file mode 100644 index fe6dc9ff..00000000 --- a/buildcc/targets/gcc/dynamic_target_gcc.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TARGETS_GCC_DYNAMIC_TARGET_GCC_H_ -#define TARGETS_GCC_DYNAMIC_TARGET_GCC_H_ - -#include "target.h" - -namespace buildcc { - -class DynamicTarget_gcc : public base::Target { -public: - DynamicTarget_gcc(const std::string &name, const base::Toolchain &toolchain, - const std::filesystem::path &target_path_relative_to_root) - : Target(name, base::TargetType::DynamicLibrary, toolchain, - target_path_relative_to_root) {} - -private: - // Compiling - virtual std::vector - CompileCommand(const std::string &input_source, - const std::string &output_source, const std::string &compiler, - const std::string &aggregated_preprocessor_flags, - const std::string &aggregated_compile_flags, - const std::string &aggregated_include_dirs) const override; - - // Linking - virtual std::vector - Link(const std::string &output_target, - const std::string &aggregated_link_flags, - const std::string &aggregated_compiled_sources, - const std::string &aggregated_lib_dirs, - const std::string &aggregated_lib_deps) const override; -}; - -} // namespace buildcc - -#endif diff --git a/buildcc/targets/gcc/static_target_gcc.cpp b/buildcc/targets/gcc/static_target_gcc.cpp deleted file mode 100644 index dbcceaf0..00000000 --- a/buildcc/targets/gcc/static_target_gcc.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "static_target_gcc.h" - -#include "internal/util.h" - -// Env -#include "assert_fatal.h" - -namespace buildcc { - -std::vector -StaticTarget_gcc::Link(const std::string &output_target, - const std::string &aggregated_link_flags, - const std::string &aggregated_compiled_sources, - const std::string &aggregated_lib_dirs, - const std::string &aggregated_lib_deps) const { - (void)aggregated_link_flags; - (void)aggregated_lib_dirs; - (void)aggregated_lib_deps; - return { - GetToolchain().GetArchiver(), - "rcs", - output_target, - aggregated_compiled_sources, - }; -} - -} // namespace buildcc diff --git a/buildcc/targets/gcc/static_target_gcc.h b/buildcc/targets/gcc/static_target_gcc.h deleted file mode 100644 index f09ddda7..00000000 --- a/buildcc/targets/gcc/static_target_gcc.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TARGETS_GCC_STATIC_TARGET_GCC_H_ -#define TARGETS_GCC_STATIC_TARGET_GCC_H_ - -#include "target.h" - -namespace buildcc { - -class StaticTarget_gcc : public base::Target { -public: - StaticTarget_gcc(const std::string &name, const base::Toolchain &toolchain, - const std::filesystem::path &target_path_relative_to_root) - : Target(name, base::TargetType::StaticLibrary, toolchain, - target_path_relative_to_root) {} - -private: - // NOTE: Compiling does not need to change - - virtual std::vector - Link(const std::string &output_target, - const std::string &aggregated_link_flags, - const std::string &aggregated_compiled_sources, - const std::string &aggregated_lib_dirs, - const std::string &aggregated_lib_deps) const override; -}; - -} // namespace buildcc - -#endif diff --git a/buildcc/targets/include/targets/target_config_interface.h b/buildcc/targets/include/targets/target_config_interface.h new file mode 100644 index 00000000..ce741013 --- /dev/null +++ b/buildcc/targets/include/targets/target_config_interface.h @@ -0,0 +1,84 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGETS_TARGET_CONFIG_INTERFACE_H_ +#define TARGETS_TARGET_CONFIG_INTERFACE_H_ + +#include "target/target.h" + +#include + +namespace buildcc { + +// USAGE: +// Derive from this class and implement specialized configs for specialized +// target/toolchains + +// NOTE: `virtual` has only been added here for an interface pattern + +// LIMITATIONS: +// We would rather be able to do this `Derived::Executable()` +// However `virtual` cannot be added with `static` hence we need to initialize +// the class before usage +// For example: `Derived().Executable()` +// This happens because the `virtual` and `static` keyword have conflicting +// definitions in C++ when associated with classes +// `virtual` - Does not relate to a class, only to the instance +// `static` - Does not relate to an instance, only to the class + +// OUR USECASE for both `virtual` and `static`: +// We would like to have the interface pattern using `virtual` +// The `static` keyword would be used to associate the specialization to a class +// rather than an instance +// This would enable `Derived::Executable()` with interfaces + +// NOTE: This implementation has been kept to show current limitations +// class ConfigInterface { +// public: +// virtual TargetConfig Executable() const = 0; +// virtual TargetConfig StaticLib() const = 0; +// virtual TargetConfig DynamicLib() const = 0; +// }; + +// * Instead of Dynamic Polymorphism we use Static Polymorphism using Templates + +// CRTP Static Polymorphism interface +// NOTE, Every specialized `Config` should derive from this +// For example, GccConfig :: public ConfigInterface +template class ConfigInterface { +public: + template static TargetConfig Generic(Args &&...args) { + return T::Generic(std::forward(args)...); + } + + template static TargetConfig Executable(Args &&...args) { + return T::Executable(std::forward(args)...); + } + + template static TargetConfig StaticLib(Args &&...args) { + return T::StaticLib(std::forward(args)...); + } + + template static TargetConfig DynamicLib(Args &&...args) { + return T::DynamicLib(std::forward(args)...); + } + + // TODO, Add more functions according to `TargetType` +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/targets/include/targets/target_custom.h b/buildcc/targets/include/targets/target_custom.h new file mode 100644 index 00000000..ea674dda --- /dev/null +++ b/buildcc/targets/include/targets/target_custom.h @@ -0,0 +1,28 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGETS_TARGET_CUSTOM_H_ +#define TARGETS_TARGET_CUSTOM_H_ + +#include "target/target.h" + +namespace buildcc { + +typedef BaseTarget Target_custom; + +} // namespace buildcc + +#endif diff --git a/buildcc/targets/include/targets/target_gcc.h b/buildcc/targets/include/targets/target_gcc.h new file mode 100644 index 00000000..2e2dfb32 --- /dev/null +++ b/buildcc/targets/include/targets/target_gcc.h @@ -0,0 +1,114 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGETS_TARGET_GCC_H_ +#define TARGETS_TARGET_GCC_H_ + +#include "target/target.h" + +#include "target_config_interface.h" + +// TODO, Combine all of these into Target_gcc +namespace buildcc { + +// Extensions +constexpr const char *const kGccExecutableExt = ""; +constexpr const char *const kGccStaticLibExt = ".a"; +constexpr const char *const kGccDynamicLibExt = ".so"; + +constexpr const char *const kGccGenericPchCompileCommand = + "{compiler} {preprocessor_flags} {include_dirs} {common_compile_flags} " + "{pch_compile_flags} {compile_flags} -o {output} -c {input}"; +constexpr const char *const kGccGenericCompileCommand = + "{compiler} {preprocessor_flags} {include_dirs} {common_compile_flags} " + "{pch_object_flags} {compile_flags} -o {output} -c {input}"; +constexpr const char *const kGccExecutableLinkCommand = + "{cpp_compiler} {link_flags} {compiled_sources} -o {output} " + "{lib_dirs} {lib_deps}"; +constexpr const char *const kGccStaticLibLinkCommand = + "{archiver} rcs {output} {compiled_sources}"; +constexpr const char *const kGccDynamicLibLinkCommand = + "{cpp_compiler} -shared {link_flags} {compiled_sources} -o {output}"; + +class GccConfig : ConfigInterface { +public: + static TargetConfig Executable() { + return DefaultGccConfig(kGccExecutableExt, kGccGenericCompileCommand, + kGccExecutableLinkCommand); + } + static TargetConfig StaticLib() { + return DefaultGccConfig(kGccStaticLibExt, kGccGenericCompileCommand, + kGccStaticLibLinkCommand); + } + static TargetConfig DynamicLib() { + return DefaultGccConfig(kGccDynamicLibExt, kGccGenericCompileCommand, + kGccDynamicLibLinkCommand); + } + +private: + static TargetConfig DefaultGccConfig(const std::string &target_ext, + const std::string &compile_command, + const std::string &link_command) { + TargetConfig config; + config.target_ext = target_ext; + config.pch_command = kGccGenericPchCompileCommand; + config.compile_command = compile_command; + config.link_command = link_command; + return config; + } +}; + +inline void DefaultGccOptions(BaseTarget &target) { + target.AddPchObjectFlag( + fmt::format("-include {}", + fs::path(target.GetPchCompilePath()).replace_extension(""))); + target.AddPchObjectFlag("-H"); +} + +class ExecutableTarget_gcc : public BaseTarget { +public: + ExecutableTarget_gcc(const std::string &name, const BaseToolchain &toolchain, + const TargetEnv &env, + const TargetConfig &config = GccConfig::Executable()) + : Target(name, TargetType::Executable, toolchain, env, config) { + DefaultGccOptions(*this); + } +}; + +class StaticTarget_gcc : public BaseTarget { +public: + StaticTarget_gcc(const std::string &name, const BaseToolchain &toolchain, + const TargetEnv &env, + const TargetConfig &config = GccConfig::StaticLib()) + : Target(name, TargetType::StaticLibrary, toolchain, env, config) { + DefaultGccOptions(*this); + } +}; + +class DynamicTarget_gcc : public BaseTarget { +public: + DynamicTarget_gcc(const std::string &name, const BaseToolchain &toolchain, + const TargetEnv &env, + const TargetConfig &config = GccConfig::DynamicLib()) + : Target(name, TargetType::DynamicLibrary, toolchain, env, config) { + AddCommonCompileFlag("-fpic"); + DefaultGccOptions(*this); + } +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/targets/include/targets/target_generic.h b/buildcc/targets/include/targets/target_generic.h new file mode 100644 index 00000000..0246c1ac --- /dev/null +++ b/buildcc/targets/include/targets/target_generic.h @@ -0,0 +1,238 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGETS_TARGET_GENERIC_H_ +#define TARGETS_TARGET_GENERIC_H_ + +#include + +#include "target/target.h" +#include "toolchain/toolchain.h" + +#include "target_gcc.h" +#include "target_mingw.h" +#include "target_msvc.h" + +namespace buildcc { + +static std::initializer_list kGenericCopyOptions = { + SyncOption::CommonCompileFlags, SyncOption::PchCompileFlags, + SyncOption::PchObjectFlags, SyncOption::AsmCompileFlags, + SyncOption::CCompileFlags, SyncOption::CppCompileFlags, + SyncOption::LinkFlags, +}; + +class GenericConfig : ConfigInterface { +public: + static TargetConfig Generic(TargetType type, ToolchainId id) { + TargetConfig config; + switch (type) { + case TargetType::Executable: + config = Executable(id); + break; + case TargetType::StaticLibrary: + config = StaticLib(id); + break; + case TargetType::DynamicLibrary: + config = DynamicLib(id); + break; + default: + env::assert_fatal("Target Type not supported"); + break; + } + + return config; + } + + static TargetConfig Executable(ToolchainId id) { + return DefaultGenericExecutable(id); + } + static TargetConfig StaticLib(ToolchainId id) { + return DefaultGenericStaticLib(id); + } + static TargetConfig DynamicLib(ToolchainId id) { + return DefaultGenericDynamicLib(id); + } + +private: + static TargetConfig DefaultGenericExecutable(ToolchainId id) { + TargetConfig config; + switch (id) { + case ToolchainId::Gcc: + config = GccConfig::Executable(); + break; + case ToolchainId::Msvc: + config = MsvcConfig::Executable(); + break; + case ToolchainId::MinGW: + config = MingwConfig::Executable(); + break; + case ToolchainId::Clang: + default: + env::assert_fatal("Compiler ID not supported"); + break; + } + + return config; + } + + static TargetConfig DefaultGenericStaticLib(ToolchainId id) { + TargetConfig config; + switch (id) { + case ToolchainId::Gcc: + config = GccConfig::StaticLib(); + break; + case ToolchainId::Msvc: + config = MsvcConfig::StaticLib(); + break; + case ToolchainId::MinGW: + config = MingwConfig::StaticLib(); + break; + case ToolchainId::Clang: + default: + env::assert_fatal("Compiler ID not supported"); + break; + } + + return config; + } + + static TargetConfig DefaultGenericDynamicLib(ToolchainId id) { + TargetConfig config; + switch (id) { + case ToolchainId::Gcc: + config = GccConfig::DynamicLib(); + break; + case ToolchainId::Msvc: + config = MsvcConfig::DynamicLib(); + break; + case ToolchainId::MinGW: + config = MingwConfig::DynamicLib(); + break; + case ToolchainId::Clang: + default: + env::assert_fatal("Compiler ID not supported"); + break; + } + + return config; + } +}; + +class ExecutableTarget_generic : public BaseTarget { +public: + ExecutableTarget_generic(const std::string &name, + const BaseToolchain &toolchain, const TargetEnv &env, + const env::optional &config = {}) + : Target(name, TargetType::Executable, toolchain, env, + config.value_or(GenericConfig::Executable(toolchain.GetId()))) { + switch (toolchain.GetId()) { + case ToolchainId::Gcc: + Copy(ExecutableTarget_gcc(name, toolchain, env), kGenericCopyOptions); + break; + case ToolchainId::Msvc: + Copy(ExecutableTarget_msvc(name, toolchain, env), kGenericCopyOptions); + break; + case ToolchainId::MinGW: + Copy(ExecutableTarget_mingw(name, toolchain, env), kGenericCopyOptions); + break; + case ToolchainId::Clang: + default: + env::assert_fatal("Compiler ID not supported"); + break; + } + } + ~ExecutableTarget_generic() {} +}; + +class StaticTarget_generic : public BaseTarget { +public: + StaticTarget_generic(const std::string &name, const BaseToolchain &toolchain, + const TargetEnv &env, + const env::optional &config = {}) + : Target(name, TargetType::StaticLibrary, toolchain, env, + config.value_or(GenericConfig::StaticLib(toolchain.GetId()))) { + switch (toolchain.GetId()) { + case ToolchainId::Gcc: + Copy(StaticTarget_gcc(name, toolchain, env), kGenericCopyOptions); + break; + case ToolchainId::Msvc: + Copy(StaticTarget_msvc(name, toolchain, env), kGenericCopyOptions); + break; + case ToolchainId::MinGW: + Copy(StaticTarget_mingw(name, toolchain, env), kGenericCopyOptions); + break; + case ToolchainId::Clang: + default: + env::assert_fatal("Compiler ID not supported"); + break; + } + } +}; + +class DynamicTarget_generic : public BaseTarget { +public: + DynamicTarget_generic(const std::string &name, const BaseToolchain &toolchain, + const TargetEnv &env, + const env::optional &config = {}) + : Target(name, TargetType::DynamicLibrary, toolchain, env, + config.value_or(GenericConfig::DynamicLib(toolchain.GetId()))) { + switch (toolchain.GetId()) { + case ToolchainId::Gcc: + Copy(DynamicTarget_gcc(name, toolchain, env), kGenericCopyOptions); + break; + case ToolchainId::Msvc: + Copy(DynamicTarget_msvc(name, toolchain, env), kGenericCopyOptions); + break; + case ToolchainId::MinGW: + Copy(DynamicTarget_mingw(name, toolchain, env), kGenericCopyOptions); + break; + case ToolchainId::Clang: + default: + env::assert_fatal("Compiler ID not supported"); + break; + } + } +}; + +class Target_generic : public BaseTarget { +public: + Target_generic(const std::string &name, TargetType type, + const BaseToolchain &toolchain, const TargetEnv &env, + const env::optional &config = {}) + : Target( + name, type, toolchain, env, + config.value_or(GenericConfig::Generic(type, toolchain.GetId()))) { + switch (type) { + case TargetType::Executable: + Copy(ExecutableTarget_generic(name, toolchain, env), kGenericCopyOptions); + break; + case TargetType::StaticLibrary: + Copy(StaticTarget_generic(name, toolchain, env), kGenericCopyOptions); + break; + case TargetType::DynamicLibrary: + Copy(DynamicTarget_generic(name, toolchain, env), kGenericCopyOptions); + break; + default: + env::assert_fatal("Compiler ID not supported"); + break; + } + } +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/targets/include/targets/target_mingw.h b/buildcc/targets/include/targets/target_mingw.h new file mode 100644 index 00000000..abaeeb38 --- /dev/null +++ b/buildcc/targets/include/targets/target_mingw.h @@ -0,0 +1,84 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGETS_TARGET_MINGW_H_ +#define TARGETS_TARGET_MINGW_H_ + +#include "target/target.h" + +#include "target_config_interface.h" +#include "target_gcc.h" + +// MinGW +// ".exe", ".a", ".so" -> (x86_64-w64-mingw32) +namespace buildcc { + +// Extensions +constexpr const char *const kMingwExecutableExt = ".exe"; +constexpr const char *const kMingwDynamicLibExt = ".dll"; + +constexpr const char *const kMingwDynamicLibLinkCommand = + "{cpp_compiler} -shared {link_flags} {compiled_sources} -o {output} " + "-Wl,--out-implib,{output}.a"; + +class MingwConfig : ConfigInterface { +public: + static TargetConfig Executable() { + return DefaultMingwConfig(kMingwExecutableExt, kGccGenericCompileCommand, + kGccExecutableLinkCommand); + } + + static TargetConfig StaticLib() { return GccConfig::StaticLib(); } + + static TargetConfig DynamicLib() { + return DefaultMingwConfig(kMingwDynamicLibExt, kGccGenericCompileCommand, + kMingwDynamicLibLinkCommand); + } + +private: + static TargetConfig DefaultMingwConfig(const std::string &target_ext, + const std::string &compile_command, + const std::string &link_command) { + TargetConfig config; + config.target_ext = target_ext; + config.pch_command = kGccGenericPchCompileCommand; + config.compile_command = compile_command; + config.link_command = link_command; + return config; + } +}; + +class ExecutableTarget_mingw : public ExecutableTarget_gcc { +public: + ExecutableTarget_mingw(const std::string &name, + const BaseToolchain &toolchain, const TargetEnv &env, + const TargetConfig &config = MingwConfig::Executable()) + : ExecutableTarget_gcc(name, toolchain, env, config) {} +}; + +typedef StaticTarget_gcc StaticTarget_mingw; + +class DynamicTarget_mingw : public DynamicTarget_gcc { +public: + DynamicTarget_mingw(const std::string &name, const BaseToolchain &toolchain, + const TargetEnv &env, + const TargetConfig &config = MingwConfig::DynamicLib()) + : DynamicTarget_gcc(name, toolchain, env, config) {} +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/targets/include/targets/target_msvc.h b/buildcc/targets/include/targets/target_msvc.h new file mode 100644 index 00000000..7baaae88 --- /dev/null +++ b/buildcc/targets/include/targets/target_msvc.h @@ -0,0 +1,130 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGETS_TARGET_MSVC_H_ +#define TARGETS_TARGET_MSVC_H_ + +#include "target/target.h" + +#include "target_config_interface.h" + +// TODO, Combine all of these into Target_msvc +namespace buildcc { + +// MSVC Constants +constexpr const char *const kMsvcExecutableExt = ".exe"; +constexpr const char *const kMsvcStaticLibExt = ".lib"; +// Why is `kWinDynamicLibExt != .dll` but `.lib` instead? +// See `kMsvcDynamicLibLinkCommand` +// IMPLIB .lib stubs are what is linked during link time +// OUT .dll needs to be present in the executable folder during runtime +constexpr const char *const kMsvcDynamicLibExt = ".lib"; + +constexpr const char *const kMsvcPchCompileCommand = + "{compiler} {preprocessor_flags} {include_dirs} {common_compile_flags} " + "/Yc{input} /FI{input} /Fp{output} {pch_compile_flags} {compile_flags} " + "/Fo{pch_object_output} /c {input_source}"; +// TODO, Split this into individual CompileCommands if any differences occur +constexpr const char *const kMsvcCompileCommand = + "{compiler} {preprocessor_flags} {include_dirs} {common_compile_flags} " + "{pch_object_flags} {compile_flags} /Fo{output} /c {input}"; +constexpr const char *const kMsvcExecutableLinkCommand = + "{linker} {link_flags} {lib_dirs} /OUT:{output} {lib_deps} " + "{compiled_sources} {pch_object_output}"; +constexpr const char *const kMsvcStaticLibLinkCommand = + "{archiver} {link_flags} /OUT:{output} {compiled_sources} " + "{pch_object_output}"; +constexpr const char *const kMsvcDynamicLibLinkCommand = + "{linker} /DLL {link_flags} /OUT:{output}.dll /IMPLIB:{output} " + "{compiled_sources} {pch_object_output}"; + +class MsvcConfig : ConfigInterface { +public: + static TargetConfig Executable() { + return DefaultMsvcConfig(kMsvcExecutableExt, kMsvcCompileCommand, + kMsvcExecutableLinkCommand); + } + static TargetConfig StaticLib() { + return DefaultMsvcConfig(kMsvcStaticLibExt, kMsvcCompileCommand, + kMsvcStaticLibLinkCommand); + } + static TargetConfig DynamicLib() { + return DefaultMsvcConfig(kMsvcDynamicLibExt, kMsvcCompileCommand, + kMsvcDynamicLibLinkCommand); + } + +private: + static TargetConfig DefaultMsvcConfig(const std::string &target_ext, + const std::string &compile_command, + const std::string &link_command) { + TargetConfig config; + config.target_ext = target_ext; + config.pch_command = kMsvcPchCompileCommand; + config.compile_command = compile_command; + config.link_command = link_command; + return config; + } +}; + +inline void DefaultMsvcOptions(BaseTarget &target) { + target.AddCCompileFlag("/nologo"); + target.AddCppCompileFlag("/nologo"); + target.AddCppCompileFlag("/EHsc"); // TODO, Might need to remove this + target.AddLinkFlag("/nologo"); + target.AddPchObjectFlag(fmt::format("/Yu{}", target.GetPchHeaderPath())); + target.AddPchObjectFlag(fmt::format("/FI{}", target.GetPchHeaderPath())); + target.AddPchObjectFlag(fmt::format("/Fp{}", target.GetPchCompilePath())); +} + +class ExecutableTarget_msvc : public BaseTarget { +public: + ExecutableTarget_msvc(const std::string &name, const BaseToolchain &toolchain, + const TargetEnv &env, + const TargetConfig &config = MsvcConfig::Executable()) + : Target(name, TargetType::Executable, toolchain, env, config) { + DefaultMsvcOptions(*this); + } +}; + +class StaticTarget_msvc : public BaseTarget { +public: + StaticTarget_msvc(const std::string &name, const BaseToolchain &toolchain, + const TargetEnv &env, + const TargetConfig &config = MsvcConfig::StaticLib()) + : Target(name, TargetType::StaticLibrary, toolchain, env, config) { + DefaultMsvcOptions(*this); + } +}; + +class DynamicTarget_msvc : public BaseTarget { +public: + DynamicTarget_msvc(const std::string &name, const BaseToolchain &toolchain, + const TargetEnv &env, + const TargetConfig &config = MsvcConfig::DynamicLib()) + : Target(name, TargetType::DynamicLibrary, toolchain, env, config), + dll_(fmt::format("{}.dll", GetTargetPath().string())) { + DefaultMsvcOptions(*this); + } + + const fs::path &GetDllPath() { return dll_; } + +private: + fs::path dll_; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/targets/msvc/CMakeLists.txt b/buildcc/targets/msvc/CMakeLists.txt deleted file mode 100644 index 41191621..00000000 --- a/buildcc/targets/msvc/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -# Target_msvc lib -m_clangtidy("target_msvc") -add_library(target_msvc - executable_target_msvc.cpp - static_target_msvc.cpp - dynamic_target_msvc.cpp - - executable_target_msvc.h - static_target_msvc.h - dynamic_target_msvc.h -) -target_include_directories(target_msvc PUBLIC - $ - $ -) -target_link_libraries(target_msvc PRIVATE - target -) -target_compile_options(target_msvc PRIVATE ${BUILD_COMPILE_FLAGS}) -target_link_options(target_msvc PRIVATE ${BUILD_LINK_FLAGS}) - -# TODO, Add Target_msvc tests - -# Target_msvc Install -if (${BUILDCC_INSTALL}) - install(TARGETS target_msvc DESTINATION lib EXPORT target_msvcConfig) - install(FILES - ${CMAKE_CURRENT_SOURCE_DIR}/executable_target_msvc.h - ${CMAKE_CURRENT_SOURCE_DIR}/static_target_msvc.h - ${CMAKE_CURRENT_SOURCE_DIR}/dynamic_target_msvc.h - DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}" - ) - install(EXPORT target_msvcConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/target_msvc") -endif() diff --git a/buildcc/targets/msvc/dynamic_target_msvc.cpp b/buildcc/targets/msvc/dynamic_target_msvc.cpp deleted file mode 100644 index a16d3125..00000000 --- a/buildcc/targets/msvc/dynamic_target_msvc.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "dynamic_target_msvc.h" - -#include "fmt/format.h" - -namespace buildcc { - -std::vector DynamicTarget_msvc::CompileCommand( - const std::string &input_source, const std::string &output_source, - const std::string &compiler, - const std::string &aggregated_preprocessor_flags, - const std::string &aggregated_compile_flags, - const std::string &aggregated_include_dirs) const { - return { - compiler, - aggregated_preprocessor_flags, - aggregated_include_dirs, - aggregated_compile_flags, - fmt::format("/Fo{}", output_source), - "/c", - input_source, - }; -} - -// Linking -std::vector -DynamicTarget_msvc::Link(const std::string &output_target, - const std::string &aggregated_link_flags, - const std::string &aggregated_compiled_sources, - const std::string &aggregated_lib_dirs, - const std::string &aggregated_lib_deps) const { - (void)aggregated_lib_dirs; - (void)aggregated_lib_deps; - return { - GetToolchain().GetLinker(), - "/DLL", - aggregated_link_flags, - fmt::format("/OUT:{}.dll", output_target), - fmt::format("/IMPLIB:{}", output_target), - aggregated_compiled_sources, - }; -} - -} // namespace buildcc diff --git a/buildcc/targets/msvc/dynamic_target_msvc.h b/buildcc/targets/msvc/dynamic_target_msvc.h deleted file mode 100644 index eab43445..00000000 --- a/buildcc/targets/msvc/dynamic_target_msvc.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TARGETS_MSVC_DYNAMIC_TARGET_MSVC_H_ -#define TARGETS_MSVC_DYNAMIC_TARGET_MSVC_H_ - -#include "target.h" - -namespace buildcc { - -class DynamicTarget_msvc : public base::Target { -public: - DynamicTarget_msvc(const std::string &name, const base::Toolchain &toolchain, - const std::filesystem::path &target_path_relative_to_root) - : Target(name, base::TargetType::DynamicLibrary, toolchain, - target_path_relative_to_root) { - prefix_include_dir_ = "/I"; - } - -private: - // Compiling - virtual std::vector - CompileCommand(const std::string &input_source, - const std::string &output_source, const std::string &compiler, - const std::string &aggregated_preprocessor_flags, - const std::string &aggregated_compile_flags, - const std::string &aggregated_include_dirs) const override; - - // Linking - virtual std::vector - Link(const std::string &output_target, - const std::string &aggregated_link_flags, - const std::string &aggregated_compiled_sources, - const std::string &aggregated_lib_dirs, - const std::string &aggregated_lib_deps) const override; -}; - -} // namespace buildcc - -#endif diff --git a/buildcc/targets/msvc/executable_target_msvc.cpp b/buildcc/targets/msvc/executable_target_msvc.cpp deleted file mode 100644 index 98e090dc..00000000 --- a/buildcc/targets/msvc/executable_target_msvc.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "executable_target_msvc.h" - -#include "internal/util.h" - -// Env -#include "assert_fatal.h" - -#include "fmt/format.h" - -namespace buildcc { - -// Compiling -std::vector ExecutableTarget_msvc::CompileCommand( - const std::string &input_source, const std::string &output_source, - const std::string &compiler, - const std::string &aggregated_preprocessor_flags, - const std::string &aggregated_compile_flags, - const std::string &aggregated_include_dirs) const { - return { - compiler, - aggregated_preprocessor_flags, - aggregated_include_dirs, - aggregated_compile_flags, - fmt::format("/Fo{}", output_source), - "/c", - input_source, - }; -} - -// Linking -std::vector -ExecutableTarget_msvc::Link(const std::string &output_target, - const std::string &aggregated_link_flags, - const std::string &aggregated_compiled_sources, - const std::string &aggregated_lib_dirs, - const std::string &aggregated_lib_deps) const { - // clang-format off - return { - GetToolchain().GetLinker(), - aggregated_link_flags, - aggregated_lib_dirs, - fmt::format("/OUT:{}", output_target), - aggregated_lib_deps, - aggregated_compiled_sources, - }; - // clang-format on -} - -} // namespace buildcc diff --git a/buildcc/targets/msvc/executable_target_msvc.h b/buildcc/targets/msvc/executable_target_msvc.h deleted file mode 100644 index 64483f13..00000000 --- a/buildcc/targets/msvc/executable_target_msvc.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TARGETS_MSVC_EXECUTABLE_TARGET_MSVC_H_ -#define TARGETS_MSVC_EXECUTABLE_TARGET_MSVC_H_ - -#include "target.h" - -namespace buildcc { - -class ExecutableTarget_msvc : public base::Target { -public: - ExecutableTarget_msvc( - const std::string &name, const base::Toolchain &toolchain, - const std::filesystem::path &target_path_relative_to_root) - : Target(name, base::TargetType::Executable, toolchain, - target_path_relative_to_root) { - prefix_include_dir_ = "/I"; - prefix_lib_dir_ = "/LIBPATH:"; - } - -private: - // Compiling - virtual std::vector - CompileCommand(const std::string &input_source, - const std::string &output_source, const std::string &compiler, - const std::string &aggregated_preprocessor_flags, - const std::string &aggregated_compile_flags, - const std::string &aggregated_include_dirs) const override; - - // Linking - virtual std::vector - Link(const std::string &output_target, - const std::string &aggregated_link_flags, - const std::string &aggregated_compiled_sources, - const std::string &aggregated_lib_dirs, - const std::string &aggregated_lib_deps) const override; -}; - -} // namespace buildcc - -#endif diff --git a/buildcc/targets/msvc/static_target_msvc.cpp b/buildcc/targets/msvc/static_target_msvc.cpp deleted file mode 100644 index 1b416a52..00000000 --- a/buildcc/targets/msvc/static_target_msvc.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "static_target_msvc.h" - -#include "fmt/format.h" - -namespace buildcc { - -std::vector StaticTarget_msvc::CompileCommand( - const std::string &input_source, const std::string &output_source, - const std::string &compiler, - const std::string &aggregated_preprocessor_flags, - const std::string &aggregated_compile_flags, - const std::string &aggregated_include_dirs) const { - return { - compiler, - aggregated_preprocessor_flags, - aggregated_include_dirs, - aggregated_compile_flags, - fmt::format("/Fo{}", output_source), - "/c", - input_source, - }; -} - -// Linking -std::vector -StaticTarget_msvc::Link(const std::string &output_target, - const std::string &aggregated_link_flags, - const std::string &aggregated_compiled_sources, - const std::string &aggregated_lib_dirs, - const std::string &aggregated_lib_deps) const { - (void)aggregated_lib_dirs; - (void)aggregated_lib_deps; - return { - GetToolchain().GetArchiver(), - aggregated_link_flags, - fmt::format("/OUT:{}", output_target), - aggregated_compiled_sources, - }; -} - -} // namespace buildcc diff --git a/buildcc/targets/msvc/static_target_msvc.h b/buildcc/targets/msvc/static_target_msvc.h deleted file mode 100644 index 0d4f3a2b..00000000 --- a/buildcc/targets/msvc/static_target_msvc.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2021 Niket Naidu. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TARGETS_MSVC_STATIC_TARGET_MSVC_H_ -#define TARGETS_MSVC_STATIC_TARGET_MSVC_H_ - -#include "target.h" - -namespace buildcc { - -class StaticTarget_msvc : public base::Target { -public: - StaticTarget_msvc(const std::string &name, const base::Toolchain &toolchain, - const std::filesystem::path &target_path_relative_to_root) - : Target(name, base::TargetType::StaticLibrary, toolchain, - target_path_relative_to_root) { - prefix_include_dir_ = "/I"; - } - -private: - // Compiling - virtual std::vector - CompileCommand(const std::string &input_source, - const std::string &output_source, const std::string &compiler, - const std::string &aggregated_preprocessor_flags, - const std::string &aggregated_compile_flags, - const std::string &aggregated_include_dirs) const override; - - // Linking - virtual std::vector - Link(const std::string &output_target, - const std::string &aggregated_link_flags, - const std::string &aggregated_compiled_sources, - const std::string &aggregated_lib_dirs, - const std::string &aggregated_lib_deps) const override; -}; - -} // namespace buildcc - -#endif diff --git a/buildcc/toolchains/CMakeLists.txt b/buildcc/toolchains/CMakeLists.txt index 0ad01635..b1380fcd 100644 --- a/buildcc/toolchains/CMakeLists.txt +++ b/buildcc/toolchains/CMakeLists.txt @@ -1,20 +1,77 @@ -m_clangtidy("toolchain_specialized") -add_library(toolchain_specialized INTERFACE) -target_include_directories(toolchain_specialized INTERFACE - $ - $ -) -target_link_libraries(toolchain_specialized INTERFACE - toolchain +set(TOOLCHAIN_SPECIALIZED_SRCS + src/toolchain_infos.cpp + include/toolchains/toolchain_infos.h + + src/toolchain_gcc.cpp + include/toolchains/toolchain_gcc.h + include/toolchains/toolchain_mingw.h + + src/toolchain_msvc.cpp + include/toolchains/toolchain_msvc.h + + src/toolchain_aggregate.cpp + include/toolchains/toolchain_generic.h + include/toolchains/toolchain_custom.h + + include/toolchains/toolchain_specialized.h ) -# toolchain_specialized Install -if (${BUILDCC_INSTALL}) - install(TARGETS toolchain_specialized DESTINATION lib EXPORT toolchain_specializedConfig) - install(FILES - ${CMAKE_CURRENT_SOURCE_DIR}/toolchain_gcc.h - ${CMAKE_CURRENT_SOURCE_DIR}/toolchain_msvc.h - DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}" +if (${TESTING}) + add_library(mock_toolchain_specialized + ${TOOLCHAIN_SPECIALIZED_SRCS} + ) + target_include_directories(mock_toolchain_specialized PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include ) - install(EXPORT toolchain_specializedConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/toolchain_specialized") + target_compile_options(mock_toolchain_specialized PUBLIC + ${TEST_COMPILE_FLAGS} ${BUILD_COMPILE_FLAGS} + ) + target_link_options(mock_toolchain_specialized PUBLIC + ${TEST_LINK_FLAGS} ${BUILD_LINK_FLAGS} + ) + target_link_libraries(mock_toolchain_specialized PUBLIC + mock_toolchain + + CppUTest + CppUTestExt + ${TEST_LINK_LIBS} + ) + + add_executable(test_toolchain_specialized test/test_toolchain_specialized.cpp) + target_link_libraries(test_toolchain_specialized PRIVATE mock_toolchain_specialized) + + add_test(NAME test_toolchain_specialized COMMAND test_toolchain_specialized) +endif() + +if(${BUILDCC_BUILD_AS_SINGLE_LIB}) + target_sources(buildcc PRIVATE + ${TOOLCHAIN_SPECIALIZED_SRCS} + ) + target_include_directories(buildcc PUBLIC + $ + $ + ) +endif() + +if(${BUILDCC_BUILD_AS_INTERFACE}) + m_clangtidy("toolchain_specialized") + add_library(toolchain_specialized STATIC + ${TOOLCHAIN_SPECIALIZED_SRCS} + ) + target_include_directories(toolchain_specialized PUBLIC + $ + $ + ) + target_link_libraries(toolchain_specialized PUBLIC + toolchain + ) +endif() + +if (${BUILDCC_INSTALL}) + if (${BUILDCC_BUILD_AS_INTERFACE}) + # toolchain_specialized Install + install(TARGETS toolchain_specialized DESTINATION lib EXPORT toolchain_specializedConfig) + install(EXPORT toolchain_specializedConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/toolchain_specialized") + endif() + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}") endif() diff --git a/buildcc/toolchains/include/toolchains/toolchain_custom.h b/buildcc/toolchains/include/toolchains/toolchain_custom.h new file mode 100644 index 00000000..dd43dfc9 --- /dev/null +++ b/buildcc/toolchains/include/toolchains/toolchain_custom.h @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAINS_TOOLCHAIN_CUSTOM_H_ +#define TOOLCHAINS_TOOLCHAIN_CUSTOM_H_ + +#include "toolchain/toolchain.h" + +namespace buildcc { + +class Toolchain_custom : public Toolchain { +public: + // Run time basic constructor + Toolchain_custom(ToolchainId id, const std::string &name, + const ToolchainExecutables &executables, + const env::optional &op_config = {}) + : Toolchain(id, name, executables, + op_config.value_or(ToolchainConfig())) { + Initialize(); + } + + virtual ~Toolchain_custom() = default; + Toolchain_custom(const Toolchain_custom &) = delete; + +private: + void Initialize(); +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/toolchains/include/toolchains/toolchain_gcc.h b/buildcc/toolchains/include/toolchains/toolchain_gcc.h new file mode 100644 index 00000000..60b15058 --- /dev/null +++ b/buildcc/toolchains/include/toolchains/toolchain_gcc.h @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAINS_TOOLCHAIN_GCC_H_ +#define TOOLCHAINS_TOOLCHAIN_GCC_H_ + +#include "toolchain/toolchain.h" + +namespace buildcc { + +/** + * @brief Generic GCC Toolchain
+ * id = ToolchainId::Gcc
+ * name = "gcc"
+ * asm_compiler = "as"
+ * c_compiler = "gcc"
+ * cpp_compiler = "g++"
+ * archiver = "ar"
+ * linker = "ld"
+ */ +class Toolchain_gcc : public Toolchain { +public: + // Run time basic constructor + Toolchain_gcc(const std::string &name = "gcc", + const env::optional &op_executables = {}, + const env::optional &op_config = {}) + : Toolchain(ToolchainId::Gcc, name, + op_executables.value_or( + ToolchainExecutables("as", "gcc", "g++", "ar", "ld")), + op_config.value_or(ToolchainConfig())) { + Initialize(); + } + + virtual ~Toolchain_gcc() = default; + Toolchain_gcc(const Toolchain_gcc &) = delete; + +private: + void Initialize(); +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/toolchains/include/toolchains/toolchain_generic.h b/buildcc/toolchains/include/toolchains/toolchain_generic.h new file mode 100644 index 00000000..76adfb38 --- /dev/null +++ b/buildcc/toolchains/include/toolchains/toolchain_generic.h @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAINS_TOOLCHAIN_GENERIC_H_ +#define TOOLCHAINS_TOOLCHAIN_GENERIC_H_ + +#include "toolchain/toolchain.h" + +namespace buildcc { + +class Toolchain_generic { +public: + /** + * @brief Create a generic toolchain instance + * + * @return Toolchain& Returns the BaseToolchain with necessary virtual + * function overrides + * Asserts fatal if ToolchainId is not supported + */ + static Toolchain & + New(ToolchainId id, const std::string &identifier, + const env::optional &op_executables = {}, + const env::optional &op_config = {}); +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/toolchains/include/toolchains/toolchain_infos.h b/buildcc/toolchains/include/toolchains/toolchain_infos.h new file mode 100644 index 00000000..111ef2b7 --- /dev/null +++ b/buildcc/toolchains/include/toolchains/toolchain_infos.h @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAINS_TOOLCHAIN_INFOS_H_ +#define TOOLCHAINS_TOOLCHAIN_INFOS_H_ + +#include + +#include "toolchain/toolchain.h" + +namespace buildcc { + +class GlobalToolchainMetadata { +public: + static const ToolchainConfig &GetConfig(ToolchainId id); + static const ToolchainInfoCb &GetInfoCb(ToolchainId id); + +private: + struct ToolchainMetadata { + ToolchainMetadata(const ToolchainConfig &config, const ToolchainInfoCb &cb) + : config_(config), cb_(cb) {} + + ToolchainConfig config_; + ToolchainInfoCb cb_; + }; + +private: + static void Expect(ToolchainId id); + static const ToolchainMetadata &Get(ToolchainId id); + +private: + static std::unordered_map + global_toolchain_metadata_; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/toolchains/include/toolchains/toolchain_mingw.h b/buildcc/toolchains/include/toolchains/toolchain_mingw.h new file mode 100644 index 00000000..4b019169 --- /dev/null +++ b/buildcc/toolchains/include/toolchains/toolchain_mingw.h @@ -0,0 +1,59 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAINS_TOOLCHAIN_MINGW_H_ +#define TOOLCHAINS_TOOLCHAIN_MINGW_H_ + +#include "toolchain/toolchain.h" + +#include "toolchain_gcc.h" + +namespace buildcc { + +/** + * @brief Generic MinGW Toolchain
+ * id = ToolchainId::MinGW
+ * name = "gcc"
+ * asm_compiler = "as"
+ * c_compiler = "gcc"
+ * cpp_compiler = "g++"
+ * archiver = "ar"
+ * linker = "ld"
+ */ +class Toolchain_mingw : public Toolchain { +public: + // Run time basic constructor + Toolchain_mingw( + const std::string &name = "gcc", + const env::optional &op_executables = {}, + const env::optional &op_config = {}) + : Toolchain(ToolchainId::MinGW, name, + op_executables.value_or( + ToolchainExecutables("as", "gcc", "g++", "ar", "ld")), + op_config.value_or(ToolchainConfig())) { + Initialize(); + } + + virtual ~Toolchain_mingw() = default; + Toolchain_mingw(const Toolchain_mingw &) = delete; + +private: + void Initialize(); +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/toolchains/include/toolchains/toolchain_msvc.h b/buildcc/toolchains/include/toolchains/toolchain_msvc.h new file mode 100644 index 00000000..8fbb8fdf --- /dev/null +++ b/buildcc/toolchains/include/toolchains/toolchain_msvc.h @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAINS_TOOLCHAIN_MSVC_H_ +#define TOOLCHAINS_TOOLCHAIN_MSVC_H_ + +#include "toolchain/toolchain.h" + +namespace buildcc { + +/** + * @brief Generic GCC Toolchain
+ * id = ToolchainId::Msvc
+ * name = "msvc"
+ * asm_compiler = "cl"
+ * c_compiler = "cl"
+ * cpp_compiler = "cl"
+ * archiver = "lib"
+ * linker = "link"
+ */ +class Toolchain_msvc : public Toolchain { +public: + // Run time basic constructor + Toolchain_msvc(const std::string &name = "msvc", + const env::optional &op_executables = {}, + const env::optional &op_config = {}) + : Toolchain(ToolchainId::Msvc, name, + op_executables.value_or( + ToolchainExecutables("cl", "cl", "cl", "lib", "link")), + op_config.value_or(ToolchainConfig())) { + Initialize(); + } + + virtual ~Toolchain_msvc() = default; + Toolchain_msvc(const Toolchain_msvc &) = delete; + +private: + void Initialize(); +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/toolchains/include/toolchains/toolchain_specialized.h b/buildcc/toolchains/include/toolchains/toolchain_specialized.h new file mode 100644 index 00000000..5e316e09 --- /dev/null +++ b/buildcc/toolchains/include/toolchains/toolchain_specialized.h @@ -0,0 +1,32 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TOOLCHAINS_TOOLCHAIN_SPECIALIZED_H_ +#define TOOLCHAINS_TOOLCHAIN_SPECIALIZED_H_ + +// Common +#include "toolchain_infos.h" + +// Toolchain specialized implementation +#include "toolchain_gcc.h" +#include "toolchain_mingw.h" +#include "toolchain_msvc.h" + +// Aggregation +#include "toolchain_custom.h" +#include "toolchain_generic.h" + +#endif diff --git a/buildcc/toolchains/src/toolchain_aggregate.cpp b/buildcc/toolchains/src/toolchain_aggregate.cpp new file mode 100644 index 00000000..6a9526b0 --- /dev/null +++ b/buildcc/toolchains/src/toolchain_aggregate.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "toolchains/toolchain_infos.h" + +#include "toolchains/toolchain_generic.h" + +#include "toolchains/toolchain_custom.h" +#include "toolchains/toolchain_gcc.h" +#include "toolchains/toolchain_mingw.h" +#include "toolchains/toolchain_msvc.h" + +#include "env/assert_fatal.h" +#include "env/storage.h" + +namespace { + +template +buildcc::Toolchain *AddIf(const std::string &identifier, Params &&...params) { + buildcc::Toolchain *toolchain{nullptr}; + if (!buildcc::Storage::Contains(identifier)) { + toolchain = &buildcc::Storage::Add(identifier, + std::forward(params)...); + } + return toolchain; +} + +} // namespace + +namespace buildcc { + +void Toolchain_custom::Initialize() { + auto id = GetId(); + RefConfig() = GlobalToolchainMetadata::GetConfig(id); + SetToolchainInfoCb(GlobalToolchainMetadata::GetInfoCb(id)); +} + +Toolchain &Toolchain_generic::New( + ToolchainId id, const std::string &identifier, + const env::optional &op_executables, + const env::optional &op_config) { + Toolchain *toolchain{nullptr}; + switch (id) { + case ToolchainId::Gcc: + toolchain = + AddIf(identifier, identifier, op_executables, op_config); + break; + case ToolchainId::Msvc: + toolchain = AddIf(identifier, identifier, op_executables, + op_config); + break; + case ToolchainId::MinGW: + toolchain = AddIf(identifier, identifier, op_executables, + op_config); + break; + case ToolchainId::Clang: + case ToolchainId::Custom: + env::assert_fatal(op_executables.has_value(), + "ToolchainId::Custom and ToolchainId::Clang require " + "executables to be provided"); + toolchain = AddIf(identifier, id, identifier, + op_executables.value(), op_config); + break; + default: + break; + } + env::assert_fatal(toolchain != nullptr, "Toolchain could not be created"); + return *toolchain; +} + +} // namespace buildcc diff --git a/buildcc/toolchains/src/toolchain_gcc.cpp b/buildcc/toolchains/src/toolchain_gcc.cpp new file mode 100644 index 00000000..19b72402 --- /dev/null +++ b/buildcc/toolchains/src/toolchain_gcc.cpp @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "toolchains/toolchain_gcc.h" +#include "toolchains/toolchain_mingw.h" + +#include "toolchains/toolchain_infos.h" + +#include "env/command.h" + +namespace buildcc { + +void Toolchain_gcc::Initialize() { + RefConfig() = GlobalToolchainMetadata::GetConfig(ToolchainId::Gcc); + SetToolchainInfoCb(GlobalToolchainMetadata::GetInfoCb(ToolchainId::Gcc)); +} + +void Toolchain_mingw::Initialize() { + RefConfig() = GlobalToolchainMetadata::GetConfig(ToolchainId::MinGW); + SetToolchainInfoCb(GlobalToolchainMetadata::GetInfoCb(ToolchainId::MinGW)); +} + +} // namespace buildcc diff --git a/buildcc/toolchains/src/toolchain_infos.cpp b/buildcc/toolchains/src/toolchain_infos.cpp new file mode 100644 index 00000000..1b7a1fc4 --- /dev/null +++ b/buildcc/toolchains/src/toolchain_infos.cpp @@ -0,0 +1,185 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "toolchains/toolchain_infos.h" + +#include "env/assert_fatal.h" +#include "env/command.h" +#include "env/logging.h" + +namespace { + +// GCC +constexpr const char *const kGccObjExt = ".o"; +constexpr const char *const kGccPchHeaderExt = ".h"; +constexpr const char *const kGccPchCompileExt = ".gch"; +constexpr const char *const kGccPrefixIncludeDir = "-I"; +constexpr const char *const kGccPrefixLibDir = "-L"; +buildcc::ToolchainConfig GetGccToolchainConfig() { + buildcc::ToolchainConfig config; + config.obj_ext = kGccObjExt; + config.pch_header_ext = kGccPchHeaderExt; + config.pch_compile_ext = kGccPchCompileExt; + config.prefix_include_dir = kGccPrefixIncludeDir; + config.prefix_lib_dir = kGccPrefixLibDir; + return config; +} + +buildcc::env::optional +GetGccCompilerVersion(const buildcc::env::Command &command) { + std::vector stdout_data; + bool executed = buildcc::env::Command::Execute( + command.Construct("{compiler} -dumpversion"), {}, &stdout_data); + if (!executed || stdout_data.empty()) { + return {}; + } + return stdout_data[0]; +} + +buildcc::env::optional +GetGccTargetArchitecture(const buildcc::env::Command &command) { + std::vector stdout_data; + bool executed = buildcc::env::Command::Execute( + command.Construct("{compiler} -dumpmachine"), {}, &stdout_data); + if (!executed || stdout_data.empty()) { + return {}; + } + return stdout_data[0]; +} + +buildcc::env::optional +GetGccToolchainInfo(const buildcc::ToolchainExecutables &executables) { + buildcc::env::Command command; + command.AddDefaultArgument("compiler", executables.cpp_compiler); + + auto op_compiler_version = GetGccCompilerVersion(command); + auto op_target_arch = GetGccTargetArchitecture(command); + if (!op_compiler_version.has_value() || !op_target_arch.has_value()) { + return {}; + } + + buildcc::ToolchainCompilerInfo compiler_info; + compiler_info.compiler_version = op_compiler_version.value(); + compiler_info.target_arch = op_target_arch.value(); + return compiler_info; +} + +// MSVC + +constexpr const char *const kMsvcObjExt = ".obj"; +constexpr const char *const kMsvcPchHeaderExt = ".h"; +constexpr const char *const kMsvcPchCompileExt = ".pch"; +constexpr const char *const kMsvcPrefixIncludeDir = "/I"; +constexpr const char *const kMsvcPrefixLibDir = "/LIBPATH:"; +buildcc::ToolchainConfig GetMsvcToolchainConfig() { + buildcc::ToolchainConfig config; + config.obj_ext = kMsvcObjExt; + config.pch_header_ext = kMsvcPchHeaderExt; + config.pch_compile_ext = kMsvcPchCompileExt; + config.prefix_include_dir = kMsvcPrefixIncludeDir; + config.prefix_lib_dir = kMsvcPrefixLibDir; + return config; +} + +buildcc::env::optional GetMsvcCompilerVersion() { + const char *vscmd_version = getenv("VSCMD_VER"); + if (vscmd_version == nullptr) { + return {}; + } + return vscmd_version; +} + +buildcc::env::optional GetMsvcTargetArchitecture() { + // DONE, Read `VSCMD_ARG_HOST_ARCH` from env path + // DONE, Read `VSCMD_ARG_TGT_ARCH` from env path + const char *vs_host_arch = getenv("VSCMD_ARG_HOST_ARCH"); + const char *vs_target_arch = getenv("VSCMD_ARG_TGT_ARCH"); + if (vs_host_arch == nullptr || vs_target_arch == nullptr) { + return {}; + } + + // DONE, Concat them + return fmt::format("{}_{}", vs_host_arch, vs_target_arch); +} + +buildcc::env::optional +GetMsvcToolchainInfo(const buildcc::ToolchainExecutables &executables) { + (void)executables; + auto op_compiler_version = GetMsvcCompilerVersion(); + auto op_target_arch = GetMsvcTargetArchitecture(); + if (!op_compiler_version.has_value() || !op_target_arch.has_value()) { + return {}; + } + + buildcc::ToolchainCompilerInfo compiler_info; + compiler_info.compiler_version = op_compiler_version.value(); + compiler_info.target_arch = op_target_arch.value(); + return compiler_info; +} + +// + +buildcc::env::optional +GetErrorToolchainInfo(const buildcc::ToolchainExecutables &executables) { + (void)executables; + buildcc::env::log_critical(__FUNCTION__, + "ToolchainInfo does not exist for particular " + "ToolchainId. Supply your own through " + "Toolchain::SetToolchainInfoCb method."); + return {}; +} + +} // namespace + +namespace buildcc { + +std::unordered_map + GlobalToolchainMetadata::global_toolchain_metadata_{ + {ToolchainId::Gcc, + ToolchainMetadata(GetGccToolchainConfig(), GetGccToolchainInfo)}, + {ToolchainId::MinGW, + ToolchainMetadata(GetGccToolchainConfig(), GetGccToolchainInfo)}, + {ToolchainId::Clang, + ToolchainMetadata(GetGccToolchainConfig(), GetGccToolchainInfo)}, + {ToolchainId::Msvc, + ToolchainMetadata(GetMsvcToolchainConfig(), GetMsvcToolchainInfo)}, + {ToolchainId::Custom, + ToolchainMetadata(ToolchainConfig(), GetErrorToolchainInfo)}, + {ToolchainId::Undefined, + ToolchainMetadata(ToolchainConfig(), GetErrorToolchainInfo)}, + }; + +const ToolchainConfig &GlobalToolchainMetadata::GetConfig(ToolchainId id) { + Expect(id); + return Get(id).config_; +} +const ToolchainInfoCb &GlobalToolchainMetadata::GetInfoCb(ToolchainId id) { + Expect(id); + return Get(id).cb_; +} + +// PRIVATE +void GlobalToolchainMetadata::Expect(ToolchainId id) { + env::assert_fatal(global_toolchain_metadata_.find(id) != + global_toolchain_metadata_.end(), + "Invalid ToolchainId"); +} +const GlobalToolchainMetadata::ToolchainMetadata & +GlobalToolchainMetadata::Get(ToolchainId id) { + return global_toolchain_metadata_.at(id); +} + +} // namespace buildcc diff --git a/buildcc/toolchains/src/toolchain_msvc.cpp b/buildcc/toolchains/src/toolchain_msvc.cpp new file mode 100644 index 00000000..ddb047e6 --- /dev/null +++ b/buildcc/toolchains/src/toolchain_msvc.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "toolchains/toolchain_msvc.h" + +#include "toolchains/toolchain_infos.h" + +namespace buildcc { + +void Toolchain_msvc::Initialize() { + RefConfig() = GlobalToolchainMetadata::GetConfig(ToolchainId::Msvc); + SetToolchainInfoCb(GlobalToolchainMetadata::GetInfoCb(ToolchainId::Msvc)); +} + +} // namespace buildcc diff --git a/buildcc/toolchains/test/test_toolchain_specialized.cpp b/buildcc/toolchains/test/test_toolchain_specialized.cpp new file mode 100644 index 00000000..0678875e --- /dev/null +++ b/buildcc/toolchains/test/test_toolchain_specialized.cpp @@ -0,0 +1,383 @@ +#include "env/host_os_util.h" +#include "env/storage.h" +#include "env/util.h" + +#include "toolchains/toolchain_specialized.h" + +#include "expect_command.h" +#include "mock_command_copier.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(ToolchainSpecializedTestGroup) +{ + void setup() { + } + void teardown() { + mock().clear(); + } +}; +// clang-format on + +TEST(ToolchainSpecializedTestGroup, GCC) { + buildcc::Toolchain_gcc gcc; + STRCMP_EQUAL(gcc.GetName().c_str(), "gcc"); + STRCMP_EQUAL(gcc.GetAssembler().c_str(), "as"); + STRCMP_EQUAL(gcc.GetCCompiler().c_str(), "gcc"); + STRCMP_EQUAL(gcc.GetCppCompiler().c_str(), "g++"); + STRCMP_EQUAL(gcc.GetArchiver().c_str(), "ar"); + STRCMP_EQUAL(gcc.GetLinker().c_str(), "ld"); + + fs::path current_directory = fs::current_path(); + buildcc::ToolchainFindConfig find_config; + find_config.env_vars.clear(); + find_config.absolute_search_paths.push_back(current_directory); + + std::vector version_stdout{"version"}; + std::vector arch_stdout{"arch"}; + buildcc::env::m::CommandExpect_Execute(1, true, &version_stdout); + buildcc::env::m::CommandExpect_Execute(1, true, &arch_stdout); + auto info = gcc.Verify(find_config); + STRCMP_EQUAL(info.compiler_version.c_str(), "version"); + STRCMP_EQUAL(info.target_arch.c_str(), "arch"); +} + +TEST(ToolchainSpecializedTestGroup, GCC_Fail) { + buildcc::Toolchain_gcc gcc; + STRCMP_EQUAL(gcc.GetName().c_str(), "gcc"); + STRCMP_EQUAL(gcc.GetAssembler().c_str(), "as"); + STRCMP_EQUAL(gcc.GetCCompiler().c_str(), "gcc"); + STRCMP_EQUAL(gcc.GetCppCompiler().c_str(), "g++"); + STRCMP_EQUAL(gcc.GetArchiver().c_str(), "ar"); + STRCMP_EQUAL(gcc.GetLinker().c_str(), "ld"); + + fs::path current_directory = fs::current_path(); + buildcc::ToolchainFindConfig find_config; + find_config.env_vars.clear(); + find_config.absolute_search_paths.push_back(current_directory); + + { + std::vector version_stdout{"version"}; + std::vector arch_stdout{"arch"}; + buildcc::env::m::CommandExpect_Execute(1, false, &version_stdout); + buildcc::env::m::CommandExpect_Execute(1, true, &arch_stdout); + CHECK_THROWS(std::exception, gcc.Verify(find_config)); + } + + { + std::vector version_stdout; + std::vector arch_stdout{"arch"}; + buildcc::env::m::CommandExpect_Execute(1, true, &version_stdout); + buildcc::env::m::CommandExpect_Execute(1, true, &arch_stdout); + CHECK_THROWS(std::exception, gcc.Verify(find_config)); + } + + { + std::vector version_stdout{"version"}; + std::vector arch_stdout{"arch"}; + buildcc::env::m::CommandExpect_Execute(1, true, &version_stdout); + buildcc::env::m::CommandExpect_Execute(1, false, &arch_stdout); + CHECK_THROWS(std::exception, gcc.Verify(find_config)); + } + + { + std::vector version_stdout{"version"}; + std::vector arch_stdout{}; + buildcc::env::m::CommandExpect_Execute(1, true, &version_stdout); + buildcc::env::m::CommandExpect_Execute(1, true, &arch_stdout); + CHECK_THROWS(std::exception, gcc.Verify(find_config)); + } +} + +TEST(ToolchainSpecializedTestGroup, MINGW) { + buildcc::Toolchain_mingw gcc; + STRCMP_EQUAL(gcc.GetName().c_str(), "gcc"); + STRCMP_EQUAL(gcc.GetAssembler().c_str(), "as"); + STRCMP_EQUAL(gcc.GetCCompiler().c_str(), "gcc"); + STRCMP_EQUAL(gcc.GetCppCompiler().c_str(), "g++"); + STRCMP_EQUAL(gcc.GetArchiver().c_str(), "ar"); + STRCMP_EQUAL(gcc.GetLinker().c_str(), "ld"); + + fs::path current_directory = fs::current_path(); + buildcc::ToolchainFindConfig find_config; + find_config.env_vars.clear(); + find_config.absolute_search_paths.push_back(current_directory); + + std::vector version_stdout{"version"}; + std::vector arch_stdout{"arch"}; + buildcc::env::m::CommandExpect_Execute(1, true, &version_stdout); + buildcc::env::m::CommandExpect_Execute(1, true, &arch_stdout); + auto info = gcc.Verify(find_config); + STRCMP_EQUAL(info.compiler_version.c_str(), "version"); + STRCMP_EQUAL(info.target_arch.c_str(), "arch"); +} + +#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(__MINGW64__) + +TEST(ToolchainSpecializedTestGroup, MSVC) { + buildcc::Toolchain_msvc msvc; + STRCMP_EQUAL(msvc.GetName().c_str(), "msvc"); + STRCMP_EQUAL(msvc.GetAssembler().c_str(), "cl"); + STRCMP_EQUAL(msvc.GetCCompiler().c_str(), "cl"); + STRCMP_EQUAL(msvc.GetCppCompiler().c_str(), "cl"); + STRCMP_EQUAL(msvc.GetArchiver().c_str(), "lib"); + STRCMP_EQUAL(msvc.GetLinker().c_str(), "link"); + + const auto &toolchain_config = msvc.GetConfig(); + STRCMP_EQUAL(toolchain_config.obj_ext.c_str(), ".obj"); + STRCMP_EQUAL(toolchain_config.pch_header_ext.c_str(), ".h"); + STRCMP_EQUAL(toolchain_config.pch_compile_ext.c_str(), ".pch"); + STRCMP_EQUAL(toolchain_config.prefix_include_dir.c_str(), "/I"); + STRCMP_EQUAL(toolchain_config.prefix_lib_dir.c_str(), "/LIBPATH:"); + + fs::path current_directory = fs::current_path(); + buildcc::ToolchainFindConfig find_config; + find_config.env_vars.clear(); + find_config.absolute_search_paths.push_back(current_directory); + + char vscmd_ver[] = "VSCMD_VER=version"; + char vscmd_arg_host_arch[] = "VSCMD_ARG_HOST_ARCH=host"; + char vscmd_arg_tgt_arch[] = "VSCMD_ARG_TGT_ARCH=target"; + CHECK_EQUAL(putenv(vscmd_ver), 0); + CHECK_EQUAL(putenv(vscmd_arg_host_arch), 0); + CHECK_EQUAL(putenv(vscmd_arg_tgt_arch), 0); + auto info = msvc.Verify(find_config); + STRCMP_EQUAL(info.compiler_version.c_str(), "version"); + STRCMP_EQUAL(info.target_arch.c_str(), "host_target"); +} + +TEST(ToolchainSpecializedTestGroup, MSVC_Fail) { + buildcc::Toolchain_msvc msvc; + STRCMP_EQUAL(msvc.GetName().c_str(), "msvc"); + STRCMP_EQUAL(msvc.GetAssembler().c_str(), "cl"); + STRCMP_EQUAL(msvc.GetCCompiler().c_str(), "cl"); + STRCMP_EQUAL(msvc.GetCppCompiler().c_str(), "cl"); + STRCMP_EQUAL(msvc.GetArchiver().c_str(), "lib"); + STRCMP_EQUAL(msvc.GetLinker().c_str(), "link"); + + const auto &toolchain_config = msvc.GetConfig(); + STRCMP_EQUAL(toolchain_config.obj_ext.c_str(), ".obj"); + STRCMP_EQUAL(toolchain_config.pch_header_ext.c_str(), ".h"); + STRCMP_EQUAL(toolchain_config.pch_compile_ext.c_str(), ".pch"); + STRCMP_EQUAL(toolchain_config.prefix_include_dir.c_str(), "/I"); + STRCMP_EQUAL(toolchain_config.prefix_lib_dir.c_str(), "/LIBPATH:"); + + fs::path current_directory = fs::current_path(); + buildcc::ToolchainFindConfig find_config; + find_config.env_vars.clear(); + find_config.absolute_search_paths.push_back(current_directory); + + { + char vscmd_ver[] = "VSCMD_VER"; + char vscmd_arg_host_arch[] = "VSCMD_ARG_HOST_ARCH"; + char vscmd_arg_tgt_arch[] = "VSCMD_ARG_TGT_ARCH"; + CHECK_EQUAL(putenv(vscmd_ver), 0); + CHECK_EQUAL(putenv(vscmd_arg_host_arch), 0); + CHECK_EQUAL(putenv(vscmd_arg_tgt_arch), 0); + CHECK_THROWS(std::exception, msvc.Verify(find_config)); + } + + { + char vscmd_ver[] = "VSCMD_VER"; + char vscmd_arg_host_arch[] = "VSCMD_ARG_HOST_ARCH=host"; + char vscmd_arg_tgt_arch[] = "VSCMD_ARG_TGT_ARCH"; + CHECK_EQUAL(putenv(vscmd_ver), 0); + CHECK_EQUAL(putenv(vscmd_arg_host_arch), 0); + CHECK_EQUAL(putenv(vscmd_arg_tgt_arch), 0); + CHECK_THROWS(std::exception, msvc.Verify(find_config)); + } + + { + char vscmd_ver[] = "VSCMD_VER"; + char vscmd_arg_host_arch[] = "VSCMD_ARG_HOST_ARCH"; + char vscmd_arg_tgt_arch[] = "VSCMD_ARG_TGT_ARCH=target"; + CHECK_EQUAL(putenv(vscmd_ver), 0); + CHECK_EQUAL(putenv(vscmd_arg_host_arch), 0); + CHECK_EQUAL(putenv(vscmd_arg_tgt_arch), 0); + CHECK_THROWS(std::exception, msvc.Verify(find_config)); + } + + { + char vscmd_ver[] = "VSCMD_VER=version"; + char vscmd_arg_host_arch[] = "VSCMD_ARG_HOST_ARCH"; + char vscmd_arg_tgt_arch[] = "VSCMD_ARG_TGT_ARCH"; + CHECK_EQUAL(putenv(vscmd_ver), 0); + CHECK_EQUAL(putenv(vscmd_arg_host_arch), 0); + CHECK_EQUAL(putenv(vscmd_arg_tgt_arch), 0); + CHECK_THROWS(std::exception, msvc.Verify(find_config)); + } +} + +#endif + +TEST(ToolchainSpecializedTestGroup, Clang) { + buildcc::Toolchain_custom clang( + buildcc::ToolchainId::Clang, "clang", + buildcc::ToolchainExecutables("llvm-as", "clang", "clang++", "llvm-ar", + "ld")); + STRCMP_EQUAL(clang.GetName().c_str(), "clang"); + STRCMP_EQUAL(clang.GetAssembler().c_str(), "llvm-as"); + STRCMP_EQUAL(clang.GetCCompiler().c_str(), "clang"); + STRCMP_EQUAL(clang.GetCppCompiler().c_str(), "clang++"); + STRCMP_EQUAL(clang.GetArchiver().c_str(), "llvm-ar"); + STRCMP_EQUAL(clang.GetLinker().c_str(), "ld"); + + fs::path current_directory = fs::current_path(); + buildcc::ToolchainFindConfig find_config; + find_config.env_vars.clear(); + find_config.absolute_search_paths.push_back(current_directory); + + std::vector version_stdout{"version"}; + std::vector arch_stdout{"arch"}; + buildcc::env::m::CommandExpect_Execute(1, true, &version_stdout); + buildcc::env::m::CommandExpect_Execute(1, true, &arch_stdout); + auto info = clang.Verify(find_config); + STRCMP_EQUAL(info.compiler_version.c_str(), "version"); + STRCMP_EQUAL(info.target_arch.c_str(), "arch"); +} + +TEST(ToolchainSpecializedTestGroup, Global) { + CHECK_THROWS(std::exception, buildcc::GlobalToolchainMetadata::GetConfig( + (buildcc::ToolchainId)65535)); + + CHECK_THROWS(std::exception, buildcc::GlobalToolchainMetadata::GetInfoCb( + (buildcc::ToolchainId)65535)); + + CHECK_FALSE(buildcc::GlobalToolchainMetadata::GetInfoCb( + buildcc::ToolchainId::Custom)(buildcc::ToolchainExecutables()) + .has_value()); + CHECK_FALSE( + buildcc::GlobalToolchainMetadata::GetInfoCb( + buildcc::ToolchainId::Undefined)(buildcc::ToolchainExecutables()) + .has_value()); +} + +TEST(ToolchainSpecializedTestGroup, Generic) { + MemoryLeakWarningPlugin::saveAndDisableNewDeleteOverloads(); + + { + auto &gcc = + buildcc::Toolchain_generic::New(buildcc::ToolchainId::Gcc, "gcc"); + STRCMP_EQUAL(gcc.GetName().c_str(), "gcc"); + STRCMP_EQUAL(gcc.GetAssembler().c_str(), "as"); + STRCMP_EQUAL(gcc.GetCCompiler().c_str(), "gcc"); + STRCMP_EQUAL(gcc.GetCppCompiler().c_str(), "g++"); + STRCMP_EQUAL(gcc.GetArchiver().c_str(), "ar"); + STRCMP_EQUAL(gcc.GetLinker().c_str(), "ld"); + + // Already defined with same identifier + CHECK_THROWS(std::exception, buildcc::Toolchain_generic::New( + buildcc::ToolchainId::Gcc, "gcc")); + } + + { + auto &mingw = + buildcc::Toolchain_generic::New(buildcc::ToolchainId::MinGW, "mingw"); + STRCMP_EQUAL(mingw.GetName().c_str(), "mingw"); + STRCMP_EQUAL(mingw.GetAssembler().c_str(), "as"); + STRCMP_EQUAL(mingw.GetCCompiler().c_str(), "gcc"); + STRCMP_EQUAL(mingw.GetCppCompiler().c_str(), "g++"); + STRCMP_EQUAL(mingw.GetArchiver().c_str(), "ar"); + STRCMP_EQUAL(mingw.GetLinker().c_str(), "ld"); + } + + { + auto &msvc = + buildcc::Toolchain_generic::New(buildcc::ToolchainId::Msvc, "msvc"); + STRCMP_EQUAL(msvc.GetName().c_str(), "msvc"); + STRCMP_EQUAL(msvc.GetAssembler().c_str(), "cl"); + STRCMP_EQUAL(msvc.GetCCompiler().c_str(), "cl"); + STRCMP_EQUAL(msvc.GetCppCompiler().c_str(), "cl"); + STRCMP_EQUAL(msvc.GetArchiver().c_str(), "lib"); + STRCMP_EQUAL(msvc.GetLinker().c_str(), "link"); + + const auto &toolchain_config = msvc.GetConfig(); + STRCMP_EQUAL(toolchain_config.obj_ext.c_str(), ".obj"); + STRCMP_EQUAL(toolchain_config.pch_header_ext.c_str(), ".h"); + STRCMP_EQUAL(toolchain_config.pch_compile_ext.c_str(), ".pch"); + STRCMP_EQUAL(toolchain_config.prefix_include_dir.c_str(), "/I"); + STRCMP_EQUAL(toolchain_config.prefix_lib_dir.c_str(), "/LIBPATH:"); + } + + { + auto &clang = buildcc::Toolchain_generic::New( + buildcc::ToolchainId::Clang, "clang", + buildcc::ToolchainExecutables("llvm-as", "clang", "clang++", "llvm-ar", + "ld")); + STRCMP_EQUAL(clang.GetName().c_str(), "clang"); + STRCMP_EQUAL(clang.GetAssembler().c_str(), "llvm-as"); + STRCMP_EQUAL(clang.GetCCompiler().c_str(), "clang"); + STRCMP_EQUAL(clang.GetCppCompiler().c_str(), "clang++"); + STRCMP_EQUAL(clang.GetArchiver().c_str(), "llvm-ar"); + STRCMP_EQUAL(clang.GetLinker().c_str(), "ld"); + } + + { + CHECK_THROWS(std::exception, buildcc::Toolchain_generic::New( + buildcc::ToolchainId::Custom, "custom")); + } + + { + CHECK_THROWS(std::exception, + buildcc::Toolchain_generic::New( + buildcc::ToolchainId::Undefined, "undefined")); + } + + buildcc::Storage::Clear(); + MemoryLeakWarningPlugin::restoreNewDeleteOverloads(); +} + +// + +void convert_executables_to_full_path(buildcc::ToolchainExecutables &exes, + const std::string &ext) { + fs::path current_path = fs::current_path().make_preferred(); + exes.assembler = + (current_path / fmt::format("{}{}", exes.assembler, ext)).string(); + exes.c_compiler = + (current_path / fmt::format("{}{}", exes.c_compiler, ext)).string(); + exes.cpp_compiler = + (current_path / fmt::format("{}{}", exes.cpp_compiler, ext)).string(); + exes.archiver = + (current_path / fmt::format("{}{}", exes.archiver, ext)).string(); + exes.linker = (current_path / fmt::format("{}{}", exes.linker, ext)).string(); +} + +void create_dummy_executables(const buildcc::ToolchainExecutables &exes) { + buildcc::env::save_file(exes.assembler.c_str(), "", false); + buildcc::env::save_file(exes.c_compiler.c_str(), "", false); + buildcc::env::save_file(exes.cpp_compiler.c_str(), "", false); + buildcc::env::save_file(exes.archiver.c_str(), "", false); + buildcc::env::save_file(exes.linker.c_str(), "", false); +} + +int main(int ac, char **av) { + buildcc::env::m::VectorStringCopier copier; + mock().installCopier(TEST_VECTOR_STRING_TYPE, copier); + + constexpr const char *const exe_ext = + buildcc::env::get_os_executable_extension(); + std::string ext = ""; + if (exe_ext) { + ext = exe_ext; + } + buildcc::ToolchainExecutables gcc_exes("as", "gcc", "g++", "ar", "ld"); + convert_executables_to_full_path(gcc_exes, ext); + create_dummy_executables(gcc_exes); + + buildcc::ToolchainExecutables msvc_exes("cl", "cl", "cl", "lib", "link"); + convert_executables_to_full_path(msvc_exes, ext); + create_dummy_executables(msvc_exes); + + buildcc::ToolchainExecutables clang_exes("llvm-as", "clang", "clang++", + "llvm-ar", "ld"); + convert_executables_to_full_path(clang_exes, ext); + create_dummy_executables(clang_exes); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildexe/CMakeLists.txt b/buildexe/CMakeLists.txt new file mode 100644 index 00000000..b78f1030 --- /dev/null +++ b/buildexe/CMakeLists.txt @@ -0,0 +1,114 @@ +add_executable(buildexe + buildexe.cpp + + src/build_env_home.cpp + include/buildexe/build_env_home.h + + src/args_setup.cpp + include/buildexe/args_setup.h + + src/toolchain_setup.cpp + include/buildexe/toolchain_setup.h + + src/build_env_setup.cpp + include/buildexe/build_env_setup.h +) +target_sources(buildexe PRIVATE + ../bootstrap/src/build_buildcc.cpp + ../bootstrap/src/build_cli11.cpp + ../bootstrap/src/build_nlohmann_json.cpp + ../bootstrap/src/build_fmtlib.cpp + ../bootstrap/src/build_spdlog.cpp + ../bootstrap/src/build_taskflow.cpp + ../bootstrap/src/build_tpl.cpp + ../bootstrap/src/build_tl_optional.cpp +) +target_include_directories(buildexe PRIVATE + include + ../bootstrap/include +) +target_link_libraries(buildexe PRIVATE buildcc) + +# TODO, Add this only if MINGW is used +# https://github.com/msys2/MINGW-packages/issues/2303 +# Similar issue when adding the Taskflow library +if (${MINGW}) + message(WARNING "-Wl,--allow-multiple-definition for MINGW") + target_link_options(buildexe PRIVATE -Wl,--allow-multiple-definition) +endif() + +if (${BUILDCC_INSTALL}) + install(TARGETS buildexe + CONFIGURATIONS Release + RUNTIME DESTINATION bin) +endif() + +add_custom_target(run_buildexe_help + COMMAND buildexe --help-all + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + VERBATIM USES_TERMINAL +) + +# [Immediate Mode] Tpl example WIN MSVC +add_custom_target(run_buildexe_im_tpl_win_msvc + COMMAND buildexe --help-all + COMMAND buildexe --config ${CMAKE_CURRENT_SOURCE_DIR}/example_configs/tpl_win.toml --config ${CMAKE_CURRENT_SOURCE_DIR}/../bootstrap/config/toolchain_win_msvc.toml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + VERBATIM USES_TERMINAL +) + +# [Immediate Mode] Tpl example LINUX GCC +add_custom_target(run_buildexe_im_tpl_linux_gcc + COMMAND buildexe --help-all + COMMAND buildexe --config ${CMAKE_CURRENT_SOURCE_DIR}/example_configs/tpl_linux.toml --config ${CMAKE_CURRENT_SOURCE_DIR}/../bootstrap/config/toolchain_linux_gcc.toml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + VERBATIM USES_TERMINAL +) + +# [Script Mode] Simple example WIN GCC +add_custom_target(run_buildexe_sm_simple_win_gcc + COMMAND buildexe --help-all + COMMAND buildexe --config ${CMAKE_CURRENT_SOURCE_DIR}/example_configs/sm_simple_linux.toml --config ${CMAKE_CURRENT_SOURCE_DIR}/../bootstrap/config/toolchain_win_gcc.toml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../example/hybrid/simple + VERBATIM USES_TERMINAL +) + +# [Script Mode] Simple example WIN MSVC +add_custom_target(run_buildexe_sm_simple_win_msvc + COMMAND buildexe --help-all + COMMAND buildexe --config ${CMAKE_CURRENT_SOURCE_DIR}/example_configs/sm_simple_win.toml --config ${CMAKE_CURRENT_SOURCE_DIR}/../bootstrap/config/toolchain_win_msvc.toml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../example/hybrid/simple + VERBATIM USES_TERMINAL +) + +# [Script Mode] Simple example LINUX GCC +add_custom_target(run_buildexe_sm_simple_linux_gcc + COMMAND buildexe --help-all + COMMAND buildexe --config ${CMAKE_CURRENT_SOURCE_DIR}/example_configs/sm_simple_linux.toml --config ${CMAKE_CURRENT_SOURCE_DIR}/../bootstrap/config/toolchain_linux_gcc.toml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../example/hybrid/simple + VERBATIM USES_TERMINAL +) + +# [Script Mode] BuildExe libs example WIN GCC +add_custom_target(run_buildexe_libs_hybrid_win_gcc + COMMAND buildexe --help-all + COMMAND buildexe --config compile.toml --config ${CMAKE_CURRENT_SOURCE_DIR}/../bootstrap/config/toolchain_win_gcc.toml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../example/buildexe/libs + VERBATIM USES_TERMINAL +) + +# [Script Mode] BuildExe libs example WIN MSVC +add_custom_target(run_buildexe_libs_hybrid_win_msvc + COMMAND buildexe --help-all + COMMAND buildexe --config compile.toml --config ${CMAKE_CURRENT_SOURCE_DIR}/../bootstrap/config/toolchain_win_msvc.toml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../example/buildexe/libs + VERBATIM USES_TERMINAL +) + +# [Script Mode] BuildExe libs example LINUX GCC +add_custom_target(run_buildexe_libs_hybrid_linux_gcc + COMMAND buildexe --help-all + COMMAND buildexe --config compile.toml --config ${CMAKE_CURRENT_SOURCE_DIR}/../bootstrap/config/toolchain_linux_gcc.toml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../example/buildexe/libs + VERBATIM USES_TERMINAL +) diff --git a/buildexe/buildexe.cpp b/buildexe/buildexe.cpp new file mode 100644 index 00000000..a93d554f --- /dev/null +++ b/buildexe/buildexe.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "buildcc.h" + +#include "buildexe/args_setup.h" +#include "buildexe/build_env_home.h" +#include "buildexe/build_env_setup.h" +#include "buildexe/toolchain_setup.h" + +#include "bootstrap/build_buildcc.h" + +using namespace buildcc; + +constexpr const char *const kTag = "BuildExe"; + +static void clean_cb(); + +// TODO, Update BuildExeArgs with internal functions +int main(int argc, char **argv) { + // + BuildccHome::Init(); + + // + BuildExeArgs buildexe_args; + buildexe_args.Setup(argc, argv); + + // TODO, Add Verification subcommand here for OS, Compiler etc! + // os win, linux considerations + // compiler gcc, msvc considerations + // arch considerations + + // TODO, Add buildcc (git cloned) + // TODO, Add libraries (git cloned) + // TODO, Add extension (git cloned) + + Reg::Init(); + Reg::Call(Args::Clean()).Func(clean_cb); + + // Host Toolchain + auto &toolchain = buildexe_args.GetHostToolchainArg().ConstructToolchain(); + toolchain.Verify(); + + if (buildexe_args.GetBuildMode() == BuildExeMode::Script) { + host_toolchain_verify(toolchain); + } + + // Build Target + BuildEnvSetup build_setup(toolchain, buildexe_args); + Reg::Toolchain(ArgToolchainState(true)).BuildPackage(build_setup); + Reg::Run(); + + // Run Target if script mode + if (buildexe_args.GetBuildMode() == BuildExeMode::Script) { + build_setup.RunUserTarget(buildexe_args.GetScriptInfo()); + } + + // - Clang Compile Commands + plugin::ClangCompileCommands({&build_setup.GetUserTarget()}).Generate(); + + return 0; +} + +static void clean_cb() { + env::log_info(kTag, fmt::format("Cleaning {}", Project::GetBuildDir())); + fs::remove_all(Project::GetBuildDir()); +} diff --git a/buildexe/example_configs/sm_simple_linux.toml b/buildexe/example_configs/sm_simple_linux.toml new file mode 100644 index 00000000..d1993cd7 --- /dev/null +++ b/buildexe/example_configs/sm_simple_linux.toml @@ -0,0 +1,25 @@ +root_dir = "" +build_dir = "_build_buildexe_simple" + +loglevel = "debug" +clean = false + +# TODO, verification +# [verification] +# os = ["win", "linux"] + +# BuildExe run mode +# mode = "immediate" +mode = "script" + +# Target information +# [build.info] +name = "simple" +type = "executable" +relative_to_root = "" + +# [build.inputs] +srcs = ["build.cpp"] + +[script] +configs = ["build_linux.toml"] diff --git a/buildexe/example_configs/sm_simple_win.toml b/buildexe/example_configs/sm_simple_win.toml new file mode 100644 index 00000000..f76d2d04 --- /dev/null +++ b/buildexe/example_configs/sm_simple_win.toml @@ -0,0 +1,25 @@ +root_dir = "" +build_dir = "_build_buildexe_simple" + +loglevel = "debug" +clean = false + +# TODO, verification +# [verification] +# os = ["win", "linux"] + +# BuildExe run mode +# mode = "immediate" +mode = "script" + +# Target information +# [build.info] +name = "simple" +type = "executable" +relative_to_root = "" + +# [build.inputs] +srcs = ["build.cpp"] + +[script] +configs = ["build_win.toml"] diff --git a/buildexe/example_configs/tpl_linux.toml b/buildexe/example_configs/tpl_linux.toml new file mode 100644 index 00000000..0a7ff4eb --- /dev/null +++ b/buildexe/example_configs/tpl_linux.toml @@ -0,0 +1,26 @@ +root_dir = ".." +build_dir = "../_build_buildexe_tpl" + +loglevel = "trace" +clean = true + +# TODO, verification +# [verification] +# os = ["win", "linux"] + +# BuildExe run mode +mode = "immediate" +# mode = "script" + +# Target information +# [build.info] +name = "libtpl_unix" +type = "staticLibrary" +relative_to_root = "third_party/tiny-process-library" + +# [build.inputs] +srcs = ["process.cpp", "process_unix.cpp"] +includes = [""] +common_compile_flags = ["-std=c++17", "-Os"] +cpp_compile_flags = ["-Wall", "-Wextra", "-Werror"] + diff --git a/buildexe/example_configs/tpl_win.toml b/buildexe/example_configs/tpl_win.toml new file mode 100644 index 00000000..80e3fa2a --- /dev/null +++ b/buildexe/example_configs/tpl_win.toml @@ -0,0 +1,26 @@ +root_dir = ".." +build_dir = "../_build_buildexe_tpl" + +loglevel = "trace" +clean = true + +# TODO, verification +# [verification] +# os = ["win", "linux"] + +# BuildExe run mode +mode = "immediate" +# mode = "script" + +# Target information +# [build.info] +name = "libtpl_win" +type = "staticLibrary" +relative_to_root = "third_party/tiny-process-library" + +# [build.inputs] +srcs = ["process.cpp", "process_win.cpp"] +includes = [""] +common_compile_flags = ["/std:c++17", "/Ot"] +cpp_compile_flags = ["-W4", "-WX"] + diff --git a/buildexe/include/buildexe/args_setup.h b/buildexe/include/buildexe/args_setup.h new file mode 100644 index 00000000..980ee696 --- /dev/null +++ b/buildexe/include/buildexe/args_setup.h @@ -0,0 +1,107 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BUILDEXE_ARGS_SETUP_H_ +#define BUILDEXE_ARGS_SETUP_H_ + +#include "buildcc.h" + +namespace buildcc { + +enum class BuildExeMode { + Immediate, + Script, +}; + +struct ArgTargetInfo : public ArgCustom { + void Add(CLI::App &app) override; + + std::string name; + TargetType type; + fs::path relative_to_root; +}; + +struct ArgTargetInputs : public ArgCustom { + void Add(CLI::App &app) override; + + // Sources + std::vector source_files; + std::vector include_dirs; + + // External libs + std::vector lib_dirs; + std::vector external_lib_deps; + + // Flags + std::vector preprocessor_flags; + std::vector common_compile_flags; + std::vector asm_compile_flags; + std::vector c_compile_flags; + std::vector cpp_compile_flags; + std::vector link_flags; +}; + +struct ArgScriptInfo : public ArgCustom { + void Add(CLI::App &app) override; + + std::vector configs; +}; + +struct LibInfo { + std::string lib_name; + std::string absolute_lib_path; +}; + +struct ArgLibsInfo : public ArgCustom { + void Add(CLI::App &app) override; + + std::vector libs_info; + std::vector lib_build_files; +}; + +class BuildExeArgs { +public: + void Setup(int argc, char **argv); + + // Getters + ArgToolchain &GetHostToolchainArg() { return host_toolchain_arg_; } + const ArgTargetInfo &GetTargetInfo() const { return out_targetinfo_; } + const ArgTargetInputs &GetTargetInputs() const { return out_targetinputs_; } + const ArgScriptInfo &GetScriptInfo() const { return out_scriptinfo_; } + BuildExeMode GetBuildMode() const { return out_mode_; } + + const std::vector &GetLibsInfo() const { + return out_libsinfo_.libs_info; + } + const std::vector &GetLibBuildFiles() const { + return out_libsinfo_.lib_build_files; + } + +private: + void SetupBuildMode(CLI::App &app); + +private: + ArgToolchain host_toolchain_arg_; + ArgTargetInfo out_targetinfo_; + ArgTargetInputs out_targetinputs_; + ArgScriptInfo out_scriptinfo_; + ArgLibsInfo out_libsinfo_; + BuildExeMode out_mode_; +}; + +} // namespace buildcc + +#endif diff --git a/buildexe/include/buildexe/build_env_home.h b/buildexe/include/buildexe/build_env_home.h new file mode 100644 index 00000000..03911e6f --- /dev/null +++ b/buildexe/include/buildexe/build_env_home.h @@ -0,0 +1,60 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BUILDEXE_BUILD_ENV_HOME_H_ +#define BUILDEXE_BUILD_ENV_HOME_H_ + +#include "buildcc.h" + +namespace buildcc { + +class BuildccHome { +public: + static void Init(); + + static const fs::path &GetBuildccHomeDir() { + ExpectInitialized(); + return buildcc_home_; + } + static const fs::path &GetBuildccBaseDir() { + ExpectInitialized(); + return buildcc_base_; + } + static const fs::path &GetBuildccLibsDir() { + ExpectInitialized(); + return buildcc_libs_; + } + static const fs::path &GetBuildccExtensionsDir() { + ExpectInitialized(); + return buildcc_extensions_; + } + + static bool IsInitialized() { return initialized_; } + static void ExpectInitialized() { + env::assert_fatal(IsInitialized(), "BuildccHome is not initialized"); + } + +private: + static fs::path buildcc_home_; + static fs::path buildcc_base_; + static fs::path buildcc_libs_; + static fs::path buildcc_extensions_; + static bool initialized_; +}; + +} // namespace buildcc + +#endif diff --git a/buildexe/include/buildexe/build_env_setup.h b/buildexe/include/buildexe/build_env_setup.h new file mode 100644 index 00000000..afbbe321 --- /dev/null +++ b/buildexe/include/buildexe/build_env_setup.h @@ -0,0 +1,72 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BUILDEXE_BUILD_ENV_SETUP_H_ +#define BUILDEXE_BUILD_ENV_SETUP_H_ + +#include "buildcc.h" + +#include "bootstrap/build_buildcc.h" + +#include "buildexe/args_setup.h" + +namespace buildcc { + +class BuildEnvSetup { +public: + static constexpr const char *const kBuildccPackageName = "BuildccPackage"; + static constexpr const char *const kUserTargetName = "UserTarget"; + +public: + BuildEnvSetup(const BaseToolchain &toolchain, + const BuildExeArgs &buildexe_args) + : toolchain_(toolchain), buildexe_args_(buildexe_args) {} + + void Setup(const ArgToolchainState &state); + + void RunUserTarget(const ArgScriptInfo &arg_script_info); + + // Getters + StaticTarget_generic &GetBuildcc() { + return storage_.Ref(kBuildccPackageName).GetBuildcc(); + } + Target_generic &GetUserTarget() { + return storage_.Ref(kUserTargetName); + } + +private: + void ConstructUserTarget(); + void ConstructUserTargetWithBuildcc(); + + void BuildccTargetSetup(); + void UserTargetSetup(); + void UserTargetCb(); + void UserTargetWithBuildccSetup(); + void UserTargetWithLibsSetup(); + void UserTargetBuild(); + void DepUserTargetOnBuildcc(); + +private: + const BaseToolchain &toolchain_; + const BuildExeArgs &buildexe_args_; + + ArgToolchainState state_; + ScopedStorage storage_; +}; + +} // namespace buildcc + +#endif diff --git a/buildexe/include/buildexe/toolchain_setup.h b/buildexe/include/buildexe/toolchain_setup.h new file mode 100644 index 00000000..2f87b9f3 --- /dev/null +++ b/buildexe/include/buildexe/toolchain_setup.h @@ -0,0 +1,28 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BUILDEXE_TOOLCHAIN_SETUP_H_ +#define BUILDEXE_TOOLCHAIN_SETUP_H_ + +#include "buildcc.h" + +namespace buildcc { + +void host_toolchain_verify(const BaseToolchain &toolchain); + +} // namespace buildcc + +#endif diff --git a/buildexe/src/args_setup.cpp b/buildexe/src/args_setup.cpp new file mode 100644 index 00000000..edee4ada --- /dev/null +++ b/buildexe/src/args_setup.cpp @@ -0,0 +1,134 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "buildexe/args_setup.h" +#include "buildexe/build_env_home.h" + +namespace buildcc { + +constexpr const char *const kTag = "BuildExe"; + +static const std::unordered_map kBuildExeModeMap{ + {"immediate", BuildExeMode::Immediate}, + {"script", BuildExeMode::Script}, +}; + +static const std::unordered_map kTargetTypeMap{ + {"executable", TargetType::Executable}, + {"staticLibrary", TargetType::StaticLibrary}, + {"dynamicLibrary", TargetType::DynamicLibrary}, +}; + +void BuildExeArgs::Setup(int argc, char **argv) { + Args::Init() + .AddToolchain("host", "Host Toolchain", host_toolchain_arg_) + .AddCustomData(out_targetinfo_) + .AddCustomData(out_targetinputs_) + .AddCustomData(out_scriptinfo_) + .AddCustomData(out_libsinfo_) + .AddCustomCallback([&](CLI::App &app) { SetupBuildMode(app); }) + .Parse(argc, argv); +} + +void BuildExeArgs::SetupBuildMode(CLI::App &app) { + app.add_option("--mode", out_mode_, "Provide BuildExe run mode") + ->transform(CLI::CheckedTransformer(kBuildExeModeMap, CLI::ignore_case)) + ->required(); +} + +void ArgTargetInfo::Add(CLI::App &app) { + constexpr const char *const kProjectInfo = "Project Info"; + auto *project_info_app = app.add_option_group(kProjectInfo); + + project_info_app->add_option("--name", name, "Provide Target name") + ->required(); + + project_info_app->add_option("--type", type, "Provide Target Type") + ->transform(CLI::CheckedTransformer(kTargetTypeMap, CLI::ignore_case)) + ->required(); + + project_info_app + ->add_option("--relative_to_root", relative_to_root, + "Provide Target relative to root") + ->required(); +} + +void ArgTargetInputs::Add(CLI::App &app) { + constexpr const char *const kTargetInputs = "Target Inputs"; + auto *target_inputs_app = app.add_option_group(kTargetInputs); + + target_inputs_app->add_option("--srcs", source_files, "Provide source files"); + target_inputs_app->add_option("--includes", include_dirs, + "Provide include dirs"); + + target_inputs_app->add_option("--lib_dirs", lib_dirs, "Provide lib dirs"); + target_inputs_app->add_option("--external_libs", external_lib_deps, + "Provide external libs"); + + target_inputs_app->add_option("--preprocessor_flags", preprocessor_flags, + "Provide Preprocessor flags"); + target_inputs_app->add_option("--common_compile_flags", common_compile_flags, + "Provide CommonCompile Flags"); + target_inputs_app->add_option("--asm_compile_flags", asm_compile_flags, + "Provide AsmCompile Flags"); + target_inputs_app->add_option("--c_compile_flags", c_compile_flags, + "Provide CCompile Flags"); + target_inputs_app->add_option("--cpp_compile_flags", cpp_compile_flags, + "Provide CppCompile Flags"); + target_inputs_app->add_option("--link_flags", link_flags, + "Provide Link Flags"); +}; + +void ArgScriptInfo::Add(CLI::App &app) { + auto *script_args = app.add_subcommand("script"); + script_args->add_option("--configs", configs, "Config files for script mode"); +} + +void ArgLibsInfo::Add(CLI::App &app) { + auto *libs_app = app.add_subcommand("libs", "Libraries"); + std::error_code ec; + fs::directory_iterator dir_iter = + fs::directory_iterator(BuildccHome::GetBuildccLibsDir(), ec); + env::assert_fatal(ec.value() == 0, + "Cannot iterate over {BUILDCC_HOME}/libs directory"); + + for (const auto &dir : dir_iter) { + if (!dir.is_directory()) { + continue; + } + fs::path lib_path = dir.path(); + std::string lib_name = lib_path.filename().string(); + + LibInfo lib_info; + lib_info.lib_name = lib_name; + lib_info.absolute_lib_path = fmt::format("{}", lib_path); + libs_info.push_back(lib_info); + + auto add_lib_files_cb_func = [lib_path, + this](const std::vector &paths) { + for (const auto &p : paths) { + fs::path absolute_file_path = (lib_path / p).make_preferred(); + lib_build_files.push_back(absolute_file_path); + } + }; + + libs_app->add_option_function>( + fmt::format("--{}", lib_name), add_lib_files_cb_func, + fmt::format("{} library", lib_name)); + } +} + +} // namespace buildcc diff --git a/buildexe/src/build_env_home.cpp b/buildexe/src/build_env_home.cpp new file mode 100644 index 00000000..503258dc --- /dev/null +++ b/buildexe/src/build_env_home.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "buildexe/build_env_home.h" + +namespace buildcc { + +fs::path BuildccHome::buildcc_home_{""}; +fs::path BuildccHome::buildcc_base_{""}; +fs::path BuildccHome::buildcc_libs_{""}; +fs::path BuildccHome::buildcc_extensions_{""}; +bool BuildccHome::initialized_{false}; + +void BuildccHome::Init() { + env::assert_fatal(!initialized_, "BuildccHome is already initialized"); + + const char *buildcc_home = getenv("BUILDCC_HOME"); + env::assert_fatal(buildcc_home != nullptr, + "BUILDCC_HOME environment variable not defined"); + + // NOTE, Verify BUILDCC_HOME + buildcc_home_ = fs::path(buildcc_home); + buildcc_base_ = buildcc_home_ / "buildcc"; + buildcc_libs_ = buildcc_home_ / "libs"; + buildcc_extensions_ = buildcc_home_ / "extensions"; + + env::assert_fatal(fs::exists(buildcc_home_), "{BUILDCC_HOME} path not found"); + env::assert_fatal(fs::exists(buildcc_base_), + "{BUILDCC_HOME}/buildcc path not found"); + env::assert_fatal(fs::exists(buildcc_libs_), + "{BUILDCC_HOME}/libs path not found"); + env::assert_fatal(fs::exists(buildcc_extensions_), + "{BUILDCC_HOME}/extensions path not found"); + + initialized_ = true; +} + +} // namespace buildcc diff --git a/buildexe/src/build_env_setup.cpp b/buildexe/src/build_env_setup.cpp new file mode 100644 index 00000000..d36360eb --- /dev/null +++ b/buildexe/src/build_env_setup.cpp @@ -0,0 +1,217 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "buildexe/build_env_setup.h" +#include "buildexe/build_env_home.h" + +namespace buildcc { + +constexpr const char *const kTag = "BuildExe"; + +void BuildEnvSetup::Setup(const ArgToolchainState &state) { + state_ = state; + if (buildexe_args_.GetBuildMode() == BuildExeMode::Script) { + // buildcc and user target + ConstructUserTargetWithBuildcc(); + } else { + // user target + ConstructUserTarget(); + } +} + +void BuildEnvSetup::RunUserTarget(const ArgScriptInfo &arg_script_info) { + env::log_info(kTag, fmt::format("************** Running '{}' **************", + buildexe_args_.GetTargetInfo().name)); + + // Aggregate the different input build .toml files to + // `--config .toml` files + std::vector configs; + std::transform(arg_script_info.configs.begin(), arg_script_info.configs.end(), + std::back_inserter(configs), + [](const std::string &c) -> std::string { + return fmt::format("--config {}", c); + }); + std::string aggregated_configs = fmt::format("{}", fmt::join(configs, " ")); + + // Construct and execute with user target on subprocess + std::string command_str = + fmt::format("{executable} {configs}", + fmt::arg("executable", + fmt::format("{}", GetUserTarget().GetTargetPath())), + fmt::arg("configs", aggregated_configs)); + env::Command::Execute(command_str); +} + +// Private + +void BuildEnvSetup::ConstructUserTarget() { + UserTargetSetup(); + UserTargetCb(); + UserTargetBuild(); +} + +void BuildEnvSetup::ConstructUserTargetWithBuildcc() { + BuildccTargetSetup(); + UserTargetSetup(); + UserTargetCb(); + UserTargetWithBuildccSetup(); + UserTargetWithLibsSetup(); + UserTargetBuild(); + DepUserTargetOnBuildcc(); +} + +void BuildEnvSetup::BuildccTargetSetup() { + const fs::path &buildcc_base = BuildccHome::GetBuildccBaseDir(); + auto &buildcc_package = storage_.Add( + kBuildccPackageName, toolchain_, + TargetEnv(buildcc_base, buildcc_base / "_build_exe")); + buildcc_package.Setup(state_); +} + +void BuildEnvSetup::UserTargetSetup() { + const ArgTargetInfo &arg_target_info = buildexe_args_.GetTargetInfo(); + storage_.Add(kUserTargetName, arg_target_info.name, + arg_target_info.type, toolchain_, + TargetEnv(arg_target_info.relative_to_root)); +} + +/** + * @brief Adds from Arg Target Inputs + * + * Source files + * Include Dirs + * Lib Dirs + * External Lib Deps + * Preprocessor flags + * Common Compile flags + * Asm Compile flags + * C Compile flags + * Cpp Compile flags + * Link flags + */ +void BuildEnvSetup::UserTargetCb() { + const ArgTargetInputs arg_target_inputs = buildexe_args_.GetTargetInputs(); + Target_generic &user_target = GetUserTarget(); + + for (const auto &s : arg_target_inputs.source_files) { + user_target.AddSource(s); + } + for (const auto &i : arg_target_inputs.include_dirs) { + user_target.AddIncludeDir(i); + } + for (const auto &l : arg_target_inputs.lib_dirs) { + user_target.AddLibDir(l); + } + for (const auto &el : arg_target_inputs.external_lib_deps) { + user_target.AddLibDep(el); + } + for (const auto &flag : arg_target_inputs.preprocessor_flags) { + user_target.AddPreprocessorFlag(flag); + } + for (const auto &flag : arg_target_inputs.common_compile_flags) { + user_target.AddCommonCompileFlag(flag); + } + for (const auto &flag : arg_target_inputs.asm_compile_flags) { + user_target.AddAsmCompileFlag(flag); + } + for (const auto &flag : arg_target_inputs.c_compile_flags) { + user_target.AddCCompileFlag(flag); + } + for (const auto &flag : arg_target_inputs.cpp_compile_flags) { + user_target.AddCppCompileFlag(flag); + } + for (const auto &flag : arg_target_inputs.link_flags) { + user_target.AddLinkFlag(flag); + } +} + +void BuildEnvSetup::UserTargetWithBuildccSetup() { + GetUserTarget().AddLibDep(GetBuildcc()); + GetUserTarget().Insert(GetBuildcc(), { + SyncOption::PreprocessorFlags, + SyncOption::CppCompileFlags, + SyncOption::IncludeDirs, + SyncOption::LinkFlags, + SyncOption::HeaderFiles, + SyncOption::IncludeDirs, + SyncOption::LibDeps, + SyncOption::ExternalLibDeps, + }); + switch (GetUserTarget().GetToolchain().GetId()) { + case ToolchainId::MinGW: + GetUserTarget().AddLinkFlag("-Wl,--allow-multiple-definition"); + break; + default: + break; + } +} + +void BuildEnvSetup::UserTargetWithLibsSetup() { + auto &user_target = GetUserTarget(); + + // Generate buildexe_lib_dirs.h with the absolute path to library folders + // Query the information through BuildExeLibDir::[lib_folder_name] + { + constexpr const char *const kConstexprLibNameFormat = + "static constexpr const char *const {lib_name} = " + "\"{absolute_lib_dir}\";"; + constexpr const char *const kLibDirsFormat = R"(// Generated by BuildCC +#pragma once + +struct BuildExeLibDir {{ +{lib_dirs} +}}; +)"; + + const auto &libs_info = buildexe_args_.GetLibsInfo(); + std::vector lib_constants; + for (const auto &linfo : libs_info) { + std::string lib_constant = fmt::format( + kConstexprLibNameFormat, fmt::arg("lib_name", linfo.lib_name), + fmt::arg("absolute_lib_dir", linfo.absolute_lib_path)); + lib_constants.push_back(lib_constant); + } + fs::path lib_dirs_filename = + user_target.GetTargetBuildDir() / "buildexe_lib_dirs.h"; + std::string data = fmt::format( + kLibDirsFormat, fmt::arg("lib_dirs", fmt::join(lib_constants, "\r\n"))); + env::save_file(lib_dirs_filename.string().c_str(), data, false); + + user_target.AddIncludeDirAbsolute(user_target.GetTargetBuildDir(), true); + } + + // Segregate valid lib files into sources and include dirs + for (const auto &lib_build_file : buildexe_args_.GetLibBuildFiles()) { + if (user_target.GetToolchain().GetConfig().IsValidSource(lib_build_file)) { + user_target.AddSourceAbsolute(lib_build_file); + } + if (user_target.GetToolchain().GetConfig().IsValidHeader(lib_build_file)) { + user_target.AddIncludeDirAbsolute(lib_build_file.parent_path(), false); + user_target.AddHeaderAbsolute(lib_build_file); + } + } +} + +void BuildEnvSetup::UserTargetBuild() { + Reg::Toolchain(state_).Build([](BaseTarget &target) { target.Build(); }, + GetUserTarget()); +} + +void BuildEnvSetup::DepUserTargetOnBuildcc() { + Reg::Toolchain(state_).Dep(GetUserTarget(), GetBuildcc()); +} + +} // namespace buildcc diff --git a/buildexe/src/toolchain_setup.cpp b/buildexe/src/toolchain_setup.cpp new file mode 100644 index 00000000..4d483daf --- /dev/null +++ b/buildexe/src/toolchain_setup.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "buildexe/toolchain_setup.h" + +namespace { + +constexpr const char *const kTag = "BuildExe"; + +} + +namespace buildcc { + +void host_toolchain_verify(const BaseToolchain &toolchain) { + env::log_info(kTag, "*** Starting Toolchain verification ***"); + + fs::path file = Project::GetBuildDir() / "verify_host_toolchain" / + "verify_host_toolchain.cpp"; + fs::create_directories(file.parent_path()); + std::string file_data = R"(// Generated by BuildExe +#include +#include + +namespace fs = std::filesystem; + +int main() { + std::cout << "********************" << std::endl; + std::cout << "Verifying host toolchain" << std::endl; + std::cout << "Current Path: " << fs::current_path() << std::endl; + std::cout << "********************" << std::endl; + return 0; +})"; + env::save_file(path_as_string(file).c_str(), file_data, false); + + ExecutableTarget_generic target( + "verify", toolchain, TargetEnv(file.parent_path(), file.parent_path())); + + target.AddSource(file); + switch (toolchain.GetId()) { + case ToolchainId::Gcc: + case ToolchainId::MinGW: + target.AddCppCompileFlag("-std=c++17"); + break; + case ToolchainId::Msvc: + target.AddCppCompileFlag("/std:c++17"); + break; + default: + env::assert_fatal("Invalid Compiler Id"); + } + target.Build(); + + // Build + tf::Executor executor; + executor.run(target.GetTaskflow()); + executor.wait_for_all(); + env::assert_fatal(env::get_task_state() == env::TaskState::SUCCESS, + "Input toolchain could not compile host program. " + "Requires HOST toolchain"); + + // Run + bool execute = env::Command::Execute(fmt::format( + "{executable}", fmt::arg("executable", target.GetTargetPath().string()))); + env::assert_fatal(execute, "Could not execute verification target"); + + env::log_info(kTag, "*** Toolchain verification done ***"); +} + +} // namespace buildcc diff --git a/cmake/coverage/gcovr.cmake b/cmake/coverage/gcovr.cmake index c16e7994..4d97ebbe 100644 --- a/cmake/coverage/gcovr.cmake +++ b/cmake/coverage/gcovr.cmake @@ -9,15 +9,17 @@ else() message("GCOVR at ${gcovr_program}") set(GCOVR_REMOVE_OPTIONS - --exclude "(.+/)?generated(.+/)?" - --exclude "(.+/)?test(.+/)?" - --exclude "(.+/)?flatbuffers(.+/)?" + --exclude "(.+/)?third_party(.+/)?" --exclude "(.+/)?spdlog(.+/)?" + --exclude "(.+/)?fmt(.+/)?" + --exclude "(.+/)?taskflow(.+/)?" + --exclude "(.+/)?CLI11(.+/)?" --exclude "(.+/)?CppUTest(.+/)?" --exclude "(.+/)?CppUTestExt(.+/)?" + --exclude "(.+/)?mock(.+/)?" - --exclude "(.+/)?fmt(.+/)?" - --exclude "(.+/)?taskflow(.+/)?" + --exclude "(.+/)?generated(.+/)?" + --exclude "(.+/)?test(.+/)?" ) # TODO, Update diff --git a/cmake/coverage/lcov.cmake b/cmake/coverage/lcov.cmake index 6038220e..8860f459 100644 --- a/cmake/coverage/lcov.cmake +++ b/cmake/coverage/lcov.cmake @@ -12,24 +12,27 @@ else() set(LCOV_RC_OPTIONS --rc lcov_branch_coverage=1 --rc genhtml_branch_coverage=1 + --rc geninfo_no_exception_branch=1 ) set(LCOV_REMOVE_OPTIONS "/usr*" - "*/generated*" + "*third_party*" "*/CppUTestExt*" - "*/spdlog*" - "*/flatbuffers*" "*/CppUTest*" - "*/test*" "*/fmt*" + "*/spdlog*" "*/taskflow*" + "*/CLI11*" + "*/generated*" + "*/test*" "*/mock*" + "*/private*" ) add_custom_target(lcov_coverage COMMAND ${lcov_program} --zerocounters --directory ${CMAKE_SOURCE_DIR} ${LCOV_RC_OPTIONS} - COMMAND cmake --build ${CMAKE_BINARY_DIR} - COMMAND cmake --build ${CMAKE_BINARY_DIR} --target test + COMMAND cmake --build ${CMAKE_BINARY_DIR} --config Debug + COMMAND cmake --build ${CMAKE_BINARY_DIR} --target test --config Debug COMMAND ${lcov_program} --capture --directory ${CMAKE_SOURCE_DIR} --output-file ${LCOV_INITIAL_FILE} ${LCOV_RC_OPTIONS} diff --git a/cmake/flags/test_flags.cmake b/cmake/flags/test_flags.cmake index bb8d8758..5846fab7 100644 --- a/cmake/flags/test_flags.cmake +++ b/cmake/flags/test_flags.cmake @@ -1,4 +1,7 @@ -set(TEST_COMPILE_FLAGS -g -Og -fprofile-arcs -ftest-coverage -fno-omit-frame-pointer -fno-optimize-sibling-calls -fno-inline -fprofile-abs-path -) -set(TEST_LINK_FLAGS -g -Og -fprofile-arcs -ftest-coverage -fprofile-abs-path -) +if(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") + set(TEST_COMPILE_FLAGS -g -Og -fprofile-arcs -ftest-coverage -fno-omit-frame-pointer -fno-optimize-sibling-calls -fno-inline -fprofile-abs-path + ) + set(TEST_LINK_FLAGS -g -Og -fprofile-arcs -ftest-coverage -fprofile-abs-path + ) + set(TEST_LINK_LIBS gcov) +endif() diff --git a/cmake/target/cli11.cmake b/cmake/target/cli11.cmake index 29a6379e..380052e1 100644 --- a/cmake/target/cli11.cmake +++ b/cmake/target/cli11.cmake @@ -1,4 +1,4 @@ -add_subdirectory(CLI11) +add_subdirectory(third_party/CLI11) file(GLOB CLI_INCLUDE_HEADERS "${CLI11_SOURCE_DIR}/include/CLI/*.hpp") if (${BUILDCC_INSTALL}) diff --git a/cmake/target/cpputest.cmake b/cmake/target/cpputest.cmake new file mode 100644 index 00000000..ffd19450 --- /dev/null +++ b/cmake/target/cpputest.cmake @@ -0,0 +1,8 @@ +set(C++11 ON CACHE BOOL "CppUTests C++11 support") +set(CPPUTEST_FLAGS OFF CACHE BOOL "CppUTests Flags off") +set(WERROR ON CACHE BOOL "CppUTests all errors") +set(LONGLONG ON CACHE BOOL "CppUTests Long Long support") +set(TESTS OFF CACHE BOOL "CppUTests tests off") +set(TESTS_BUILD_DISCOVER OFF CACHE BOOL "CppUTests Tests discover") +set(VERBOSE_CONFIG OFF CACHE BOOL "Config print to screen") +add_subdirectory(third_party/cpputest) diff --git a/cmake/target/fmt.cmake b/cmake/target/fmt.cmake new file mode 100644 index 00000000..53cafdcf --- /dev/null +++ b/cmake/target/fmt.cmake @@ -0,0 +1,4 @@ +set(FMT_INSTALL ON CACHE BOOL "Fmt install") +add_subdirectory(third_party/fmt) +# TODO, Remove fmt library generation and install target +# set_target_properties(fmt PROPERTIES EXCLUDE_FROM_ALL ON) diff --git a/cmake/target/json.cmake b/cmake/target/json.cmake new file mode 100644 index 00000000..bc524c55 --- /dev/null +++ b/cmake/target/json.cmake @@ -0,0 +1,3 @@ +set(JSON_BuildTests OFF CACHE BOOL "JSON Unit tests") +set(JSON_Install ON CACHE BOOL "JSON Install") +add_subdirectory(third_party/json) diff --git a/cmake/target/spdlog.cmake b/cmake/target/spdlog.cmake new file mode 100644 index 00000000..26688c08 --- /dev/null +++ b/cmake/target/spdlog.cmake @@ -0,0 +1,8 @@ +# set(SPDLOG_BUILD_SHARED ON CACHE BOOL "Spdlog built as dynamic library") +set(SPDLOG_INSTALL ON CACHE BOOL "Spdlog install") +set(SPDLOG_FMT_EXTERNAL OFF CACHE BOOL "Spdlog FMT external lib") +set(SPDLOG_FMT_EXTERNAL_HO ON CACHE BOOL "Spdlog FMT header only external lib") +set(SPDLOG_ENABLE_PCH ${BUILDCC_PRECOMPILE_HEADERS} CACHE BOOL "Spdlog PCH") +add_subdirectory(third_party/spdlog) +# TODO, Remove spdlog library generation and install target +# set_target_properties(spdlog PROPERTIES EXCLUDE_FROM_ALL ON) diff --git a/cmake/target/taskflow.cmake b/cmake/target/taskflow.cmake new file mode 100644 index 00000000..5c2992ef --- /dev/null +++ b/cmake/target/taskflow.cmake @@ -0,0 +1,3 @@ +set(TF_BUILD_TESTS OFF CACHE BOOL "TF Tests") +set(TF_BUILD_EXAMPLES OFF CACHE BOOL "TF Examples") +add_subdirectory(third_party/taskflow) diff --git a/cmake/target/tl_optional.cmake b/cmake/target/tl_optional.cmake new file mode 100644 index 00000000..f7afb525 --- /dev/null +++ b/cmake/target/tl_optional.cmake @@ -0,0 +1,2 @@ +set(OPTIONAL_ENABLE_TESTS OFF CACHE BOOL "TL_OPTIONAL Tests") +add_subdirectory(third_party/tl_optional) diff --git a/cmake/target/tpl.cmake b/cmake/target/tpl.cmake index db0078d7..8d0b7782 100644 --- a/cmake/target/tpl.cmake +++ b/cmake/target/tpl.cmake @@ -1,3 +1,5 @@ +add_subdirectory(third_party/tiny-process-library) + if (${BUILDCC_INSTALL}) install(TARGETS tiny-process-library EXPORT tiny-process-library-config @@ -11,5 +13,5 @@ if (${BUILDCC_INSTALL}) DESTINATION lib/cmake/tiny-process-library ) - install(FILES tiny-process-library/process.hpp DESTINATION include) + install(FILES third_party/tiny-process-library/process.hpp DESTINATION include) endif() diff --git a/cmake/tool/clangtidy.cmake b/cmake/tool/clangtidy.cmake index 4e56cf8d..d30eb210 100644 --- a/cmake/tool/clangtidy.cmake +++ b/cmake/tool/clangtidy.cmake @@ -1,7 +1,7 @@ macro(m_clangtidy) if (${CLANGTIDY}) message("Setting ClangTidy: ON -> ${ARGV0}") - set(CMAKE_CXX_CLANG_TIDY clang-tidy -checks=-*,readability-* --format-style=file) + set(CMAKE_CXX_CLANG_TIDY clang-tidy -checks=-*,readability-*,portability-*,performance-* --format-style=file) else() message("Setting ClangTidy: OFF -> ${ARGV0}") endif() diff --git a/cmake/tool/cppcheck.cmake b/cmake/tool/cppcheck.cmake index 20d98554..9f11dbe6 100644 --- a/cmake/tool/cppcheck.cmake +++ b/cmake/tool/cppcheck.cmake @@ -13,7 +13,7 @@ if(${BUILDCC_CPPCHECK}) set(CPPCHECK_ADDITIONAL_OPTIONS --std=c++17 -q - --error-exitcode=1 + # --error-exitcode=1 --cppcheck-build-dir=${CMAKE_CURRENT_BINARY_DIR}/cppcheck_output ) set(CPPCHECK_CHECK_DIR diff --git a/cmake/tool/doxygen.cmake b/cmake/tool/doxygen.cmake index 82401577..1bd945f7 100644 --- a/cmake/tool/doxygen.cmake +++ b/cmake/tool/doxygen.cmake @@ -5,20 +5,25 @@ if (${BUILDCC_DOCUMENTATION}) message("Doxygen Found: ${DOXYGEN_FOUND}") message("Doxygen Version: ${DOXYGEN_VERSION}") - set(DOXYGEN_EXCLUDE_PATTERNS - *test/* - *mock/* - ) - set(DOXYGEN_BUILTIN_STL_SUPPORT YES) + # set(DOXYGEN_EXCLUDE_PATTERNS + # *test/* + # *mock/* + # ) set(DOXYGEN_EXTRACT_ALL YES) - set(DOXYGEN_MARKDOWN_SUPPORT YES) - set(DOXYGEN_WARN_IF_UNDOCUMENTED NO) - set(DOXYGEN_USE_MDFILE_AS_MAINPAGE ${CMAKE_CURRENT_SOURCE_DIR}/README.md) + set(DOXYGEN_WARN_IF_UNDOCUMENTED YES) + set(DOXYGEN_GENERATE_XML YES) doxygen_add_docs(doxygen_documentation - ${CMAKE_CURRENT_SOURCE_DIR}/README.md - ${CMAKE_CURRENT_SOURCE_DIR}/TODO.md - ${CMAKE_CURRENT_SOURCE_DIR}/example/README.md ${CMAKE_CURRENT_SOURCE_DIR}/buildcc COMMENT "Doxygen documentation" ) + + find_program(sphinx_build + NAMES "sphinx-build" + REQUIRED + ) + add_custom_target(sphinx_documentation + COMMAND ${sphinx_build} -b html -Dbreathe_projects.buildcc_documentation=${CMAKE_CURRENT_BINARY_DIR}/xml . ../output + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs/source + VERBATIM USES_TERMINAL + ) endif() diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..e47d5566 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,26 @@ +codecov: + require_ci_to_pass: yes + notify: + wait_for_ci: yes + +coverage: + precision: 2 + round: nearest + range: "60...100" + status: + project: + default: + target: auto + threshold: 1% + patch: + default: + target: auto + threshold: 1% + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: yes + macro: yes diff --git a/doc/developer/project_internals.md b/doc/developer/project_internals.md new file mode 100644 index 00000000..11cd2995 --- /dev/null +++ b/doc/developer/project_internals.md @@ -0,0 +1,61 @@ +# Project Internals + +## Build + +### CMakePresets (from Version 3.20) + +- See `CMakePresets.json` for GCC, MSVC and Clang configurations +```bash +# Generating +cmake --list-presets +cmake --preset=[your_preset] + +# Building +cmake --build --list-presets +cmake --build --preset=[your_preset] + +# Testing (ONLY supported on gcc) +ctest --preset=gcc_dev_all +``` + +### Custom Targets + +```bash +# Run custom target using +cd [folder] +cmake --build . --target [custom_target] +``` + +**Tools** +- cppcheck_static_analysis +- doxygen_documentation +- gcovr_coverage +- lcov_coverage + +**Examples** +- run_hybrid_simple_example_linux +- run_hybrid_simple_example_win +- run_hybrid_foolib_example_linux +- run_hybrid_foolib_example_win +- run_hybrid_externallib_example_linux +- run_hybrid_externallib_example_win +- run_hybrid_customtarget_example_linux +- run_hybrid_customtarget_example_win + +## Install + +- See the **user installation** section above + +- Read [Install target](buildcc/lib/target/cmake/target_install.cmake) + +Basic Installation steps +- Install `TARGETS` +- Install `HEADER FILES` +- Export `CONFIG` + +## Test + +- Read [Mock env](buildcc/lib/env/CMakeLists.txt) +- Read [Mock target](buildcc/lib/target/cmake/mock_target.cmake) +- Read [Test path](buildcc/lib/target/test/path/CMakeLists.txt) +- Read [Test target](buildcc/lib/target/test/target/CMakeLists.txt) diff --git a/doc/faq/why_this_lib.md b/doc/faq/why_this_lib.md index f14544cd..a44368fb 100644 --- a/doc/faq/why_this_lib.md +++ b/doc/faq/why_this_lib.md @@ -31,6 +31,10 @@ Also see [Target::BuildRecompile() in build.cpp](../../buildcc/lib/target/src/ta - For example: an array cannot be a root when writing your schema. - `flexbuffer` can be used to generate flexibile + efficient JSON. +# JSON + +- See https://github.com/coder137/build_in_cpp/issues/222 + # Fmtlib and Spdlog - Fmtlib and Spdlog are extremely popular libraries for formatting and logging. diff --git a/doc/serialization/generator_fbs.md b/doc/serialization/generator_fbs.md new file mode 100644 index 00000000..4bb351ad --- /dev/null +++ b/doc/serialization/generator_fbs.md @@ -0,0 +1,42 @@ +# Understanding `generator.fbs` + +See [generator.fbs](../../buildcc/schema/generator.fbs) + +## Current Feature Progress + +- [x] IOFileGenerator + - Currently named `base::Generator` +- [ ] TemplateFileGenerator +- [ ] CustomCallbackGenerator + +# Input/Output File Generator + +See [Input/Output File Generator API](../../buildcc/lib/target/include/target/generator.h) + +## Definition + +- An IO file generator takes user supplied command strings consisting of an executable invocation over one or more input files. +- The goal of these executable invocation over input files is to **generate** output files. Hence the name IO File Generator. +- An IO File Generator can be of the following variety + - Single input generates single output + - Single input generates multiple outputs + - Multiple inputs generate single output + - Multiple inputs generate multiple outputs + +## Correlation with schema file + +- `inputs` are physically present files on the disk + - These files can be generated by another `Target` or `Generator` but will eventually be present +- `outputs` are future files depending on input states, only path strings are stored +- `commands` are ordered list of executable invocation strings. These perform actions on `input` files. + - NOTE: TO make `commands` run in parallel if they do not need to be ordered set `parallel = true` in the Generator constructor. + +# Template File Generator + +> TODO, + + +# Custom Callback Generator + +> TODO, + diff --git a/doc/serialization/path_fbs.md b/doc/serialization/path_fbs.md new file mode 100644 index 00000000..e99a5065 --- /dev/null +++ b/doc/serialization/path_fbs.md @@ -0,0 +1,65 @@ +# Understanding `path.fbs` + +See [path.fbs](../../buildcc/schema/path.fbs) + +## Current reasoning + +- From our software architecture in the project README we can see that a Target needs to verify the physical presence of the following files. + - Source files + - Header files + - PCH files + - Lib dependencies +- Additionally we also track + - User defined compile dep files + - User defined link dep files + +For efficient rebuilds where physical presence of a **set of files** need to be verified we check the **previous loaded set** with the **current input set** + +The following states can be observed: +- FILE_REMOVED: **current_set does not have a filepath that is present in loaded_set** +- FILE_ADDED: **loaded_set does not have a filepath that is present in current_set** +- FILE_UPDATED: **loaded_set::file::stored_timestamp is older than current_set::file::stored_timestamp** + +For the **FILE_REMOVED** and **FILE_ADDED** state we check `schema::internal::Path::pathname` for any differences + +For the **FILE_UPDATED** state we check `schema::internal::Path::last_write_timestamp` for any differences + +## Future Scope + +Our rebuilds focus on 3 states +- FILE_REMOVED +- FILE_ADDED +- FILE_UPDATED + +For a future scope we can think about efficient rebuilds for FILE_UPDATED state + +- Timestamp to rebuild + - Faster to compute + - Might cause unnecessary rebuilds when a file is saved but diff does not change +- File hash to rebuild + - Slower to compute + - More efficient since it would only rebuild / be marked dirty if the file contains diffs from the previous version + +In the future instead of having + +```json +{ + "pathname": "string", + "last_write_timestamp": "uint64", +} +``` + +we can have + +```json +{ + "pathname": "string", + "rebuild_strategy": "string", +} +``` + +Now our **FILE_UPDATED** state could contain both +- Timestamp strategy +- File Hash strategy + +encompassed into the same structure diff --git a/doc/serialization/target_fbs.md b/doc/serialization/target_fbs.md new file mode 100644 index 00000000..b14fdb2e --- /dev/null +++ b/doc/serialization/target_fbs.md @@ -0,0 +1,68 @@ +# Understanding `target.fbs` + +See [target.fbs](../../buildcc/schema/target.fbs) + +## Definition + +See Target APIs present in the [../../buildcc/lib/target/include/target/api](../../buildcc/lib/target/include/target/api) folder + +- A Target is two stage procedure consisting of compiling (preprocessing + compiling) and linking. +- During compiling we can compile + - Header files to PCH files + - Source files to Object files +- During linking we take our objects and create a Target + +## Correlation with schema file + +### Metadata + +- `type` is the ssociated target type + - Executable + - Static Library + - Dynamic Library + - C++20 Modules (Future) + +### Physically present files + +- `source_files` are input files that will be compiled to object files +- `header_files` are tracked for better rebuilds + - See [faq/include_dir_vs_header_files](../faq/include_dir_vs_header_files.md) +- `pch_files` are tracked for certain headers that need to be aggregated to a single PCH output. + - If any one of these pch files change we need to recompile our PCH output. +- `lib_deps` are Targets of type + - Static Library + - Dynamic Library + - C++20 Modules (Future) + +### Links + +- `external_lib_deps` are library links + - For example: -lpthread, -lm in GCC + +### Directories + +- `include_dirs` are include directories to find header files used in source files. +- `lib_dirs`: are library directories to find external lib deps during linking. + +### Flags + +- `preprocessor_flags` are preprocessor macros added during compiling +- `common_compile_flags` are compile flags added to PCH, ASM, C and C++ sources +- `pch_compile_flags` are compile flags only added + - if pch is enabled i.e pch files are added + - during pch compile command stage +- `pch_object_flags` are compile flags only added + - if pch is enabled i.e pch files are added + - during object compile command stage + - NOTE: This is because most source -> object files depend on the generated pch files +- `asm_compile_flags` are specific compile flags added to ASM sources +- `c_compile_flags` are specific compile flags added to C sources +- `cpp_compile_flags` are specific compile flags added to CPP sources +- `link_flags` are specific link flags added during `link_command` stage + +### User defined dependencies + +- `compile_dependencies` are user compile dependencies that cause all sources to recompile if any compile dependency is REMOVED, ADDED or UPDATED +- `link_dependencies` are user link dependencies that cause target to recompile if any link dependency is REMOVED, ADDED or UPDATED + +> TODO, Add pch_dependencies diff --git a/doc/software_architecture/buildcc_core_dep.PNG b/doc/software_architecture/buildcc_core_dep.PNG deleted file mode 100644 index 8f44a897..00000000 Binary files a/doc/software_architecture/buildcc_core_dep.PNG and /dev/null differ diff --git a/doc/software_architecture/buildcc_interface_lib.PNG b/doc/software_architecture/buildcc_interface_lib.PNG new file mode 100644 index 00000000..58a0d56b Binary files /dev/null and b/doc/software_architecture/buildcc_interface_lib.PNG differ diff --git a/doc/software_architecture/buildcc_single_lib.PNG b/doc/software_architecture/buildcc_single_lib.PNG new file mode 100644 index 00000000..767441d2 Binary files /dev/null and b/doc/software_architecture/buildcc_single_lib.PNG differ diff --git a/doc/software_architecture/create_uml_diagrams.md b/doc/software_architecture/create_uml_diagrams.md new file mode 100644 index 00000000..07fc24c6 --- /dev/null +++ b/doc/software_architecture/create_uml_diagrams.md @@ -0,0 +1,21 @@ +# Create UML Diagrams in VSCode using PlatUML + +- Download the VSCode extension `PlantUML by jebbs` +- See the [PlantUML website](https://plantuml.com/) + +## Writing a basic PlantUML script in Markdown + +- Name your file `your_file.md` +- Inside the file + +``` +@startuml + +{your plantuml script here} + +@enduml +``` + +## This project + +- [Dependency Graph](uml/dependency_graph.md) diff --git a/doc/software_architecture/uml/dependency_graph.md b/doc/software_architecture/uml/dependency_graph.md new file mode 100644 index 00000000..12ad8add --- /dev/null +++ b/doc/software_architecture/uml/dependency_graph.md @@ -0,0 +1,49 @@ +@startuml + +[*] --> Compile +Compile --> Link +Link --> [*] + +state Compile { +SourceFile --> Compiler +HeaderFile --> Compiler +IncludeDirs --> Compiler +PreprocessorFlags --> Compiler +CompilerFlags --> Compiler + +Compiler --> ObjectFile +Compiler --> PrecompileHeader + +PrecompileHeader --> ObjectFile + +SourceFile : Path + Timestamp +HeaderFile : Path + Timestamp +IncludeDirs : Path +PreprocessorFlags : String +CompilerFlags : String +Compiler : Toolchain +PrecompileHeader : Path +ObjectFile : Path +} + +Compile : {1 ... N} + +state Link { +Library --> Linker +Module --> Linker +LinkDirs --> Linker +LinkFlags --> Linker + +Linker --> Executable +Linker --> Library +Linker --> Module + +Library : Path + Timestamp +Module : Path + Timestamp +Executable : Path + Timestamp +Linker : Toolchain +LinkDirs : Path +LinkFlags : String +} + +@enduml diff --git a/doc/software_architecture/uml/dependency_graph.png b/doc/software_architecture/uml/dependency_graph.png new file mode 100644 index 00000000..7c91b063 Binary files /dev/null and b/doc/software_architecture/uml/dependency_graph.png differ diff --git a/doc/software_architecture/uml/generator_tasks.md b/doc/software_architecture/uml/generator_tasks.md new file mode 100644 index 00000000..bf8c4023 --- /dev/null +++ b/doc/software_architecture/uml/generator_tasks.md @@ -0,0 +1,20 @@ +@startuml + +[*] --> GeneratorStartTask +GeneratorStartTask --> GenerateTask : Success +GenerateTask --> GeneratorEndTask : Success / Failure +GeneratorEndTask --> [*] + +state GenerateTask { +} + +GeneratorStartTask --> GeneratorEndTask : Failure + +GeneratorStartTask : GetEnvTaskState +GenerateTask : PreGenerate +GenerateTask : Generate +GenerateTask : PostGenerate +GeneratorEndTask : Store +GeneratorEndTask : UpdateEnvTaskStateIfFailure + +@enduml diff --git a/doc/software_architecture/uml/generator_tasks.png b/doc/software_architecture/uml/generator_tasks.png new file mode 100644 index 00000000..9b7053b6 Binary files /dev/null and b/doc/software_architecture/uml/generator_tasks.png differ diff --git a/doc/software_architecture/uml/target_tasks.md b/doc/software_architecture/uml/target_tasks.md new file mode 100644 index 00000000..630d1ae2 --- /dev/null +++ b/doc/software_architecture/uml/target_tasks.md @@ -0,0 +1,53 @@ +@startuml + +[*] --> TargetStartTask +TargetStartTask --> PchTask : Success +PchTask --> ObjectTask : Success +ObjectTask --> LinkTask : Success +LinkTask --> TargetEndTask : Success / Failure +TargetEndTask --> [*] + +state PchTask { + Header1 --> AggregateToSingleHeader + Header2 --> AggregateToSingleHeader + HeaderN --> AggregateToSingleHeader + AggregateToSingleHeader --> CompilePch + CompilePch --> StorePch + StorePch : StorePchHeaderFiles + StorePch: StorePchCompiledFile +} + +state ObjectTask { + Source1 --> Object1 + Source2 --> Object2 + SourceN --> ObjectN + Object1 --> MutexLock + Object2 --> MutexLock + ObjectN --> MutexLock + MutexLock --> Store + Store --> MutexUnlock + + Store : SourceFiles + Store : ObjectFiles +} + +state LinkTask { + AggregateObjects --> LinkTarget + LinkTarget --> StoreTarget + + StoreTarget : TargetFile +} + +TargetStartTask --> TargetEndTask : Failure +PchTask --> TargetEndTask : Failure +ObjectTask --> TargetEndTask : Failure + +TargetStartTask : GetEnvTaskState +TargetEndTask : StorePchHeaderFiles +TargetEndTask : StorePchCompileFiles +TargetEndTask : SourceFiles +TargetEndTask : ObjectFiles +TargetEndTask : TargetFile +TargetEndTask : UpdateEnvTaskStateIfFailure + +@enduml diff --git a/doc/software_architecture/uml/target_tasks.png b/doc/software_architecture/uml/target_tasks.png new file mode 100644 index 00000000..791d7634 Binary files /dev/null and b/doc/software_architecture/uml/target_tasks.png differ diff --git a/doc/target/custom_commands.md b/doc/target/custom_commands.md new file mode 100644 index 00000000..9037693d --- /dev/null +++ b/doc/target/custom_commands.md @@ -0,0 +1,68 @@ +# Target commands for Compile and Link + +When constructing custom commands we need to supply our own pattern to the buildsystem + +This is done by overriding the 3 `base::Target::Config` strings + +```cpp +base::Target::Config config; + +config.pch_command = "{compiler} {preprocessor_flags} {include_dirs} {common_compile_flags} {pch_compile_flags} {compile_flags} -o {output} -c {input}"; + +config.compile_command = "{compiler} {preprocessor_flags} {include_dirs} {common_compile_flags} {pch_object_flags} {compile_flags} -o {output} -c {input}"; + +config.link_command = "{cpp_compiler} {link_flags} {compiled_sources} -o {output} {lib_dirs} {lib_deps}"; +``` + +- See [GCC specific overrides](../../buildcc/targets/include/targets/target_gcc.h) +- See [MSVC specific overrides](../../buildcc/targets/include/targets/target_msvc.h) + +# General + +The following `{}` commands are available to `pch_command`, `compile_command` and `link_command` + +See [build.cpp Target::Build API](../../buildcc/lib/target/src/target/build.cpp) + +- `include_dirs`: Aggregated include directories for header files +- `lib_dirs`: Aggregated lib directories for external libraries +- `preprocessor_flags`: Preprocessor definitions +- `common_compile_flags`: Common compile flags for `PCH`, `ASM`, `C` and `CPP` files +- `link_flags`: Flags supplied during linking +- `asm_compiler`: Assembly compiler +- `c_compiler`: C compiler +- `cpp_compiler`: C++ compiler +- `archiver`: Archiver for Static Libraries +- `linker`: Linker usually used during the Linking phase / Library creation + +> NOTE, When PCH is not used these options are aggregated to an empty string ("") + +- `pch_compile_flags`: PCH flags applied when compiling a PCH +- `pch_object_flags`: PCH flags applied to object files after compiling a PCH +- `pch_object_output`: [Specific use case] Certain compilers (MSVC) require source/object with the header inputs. `input_source` (mentioned below) is added locally during `pch_command` translation whereas `pch_object_output` is added globally. (However the `pch_object_output` will most likely be used by `link_command` translation if added by the user.) + +# PCH Specific + +See [CompilePch::ConstructCompileCommand API](../../buildcc/lib/target/src/target/friend/compile_pch.cpp) + +- `compiler`: Selects CPP compiler if project contains CPP source else C compiler +- `compile_flags`: Selects CPP flags if project contains CPP source else C flags +- `output`: PCH output path +- `input`: PCH input generated path (Headers are aggregated into a .h file) +- `input_source`: PCH input source generated path (Dummy source file with corresponding extension, .c for C source and .cpp for C++ source) + +# Compile Specific + +See [CompileObject::CacheCompileCommands API](../../buildcc/lib/target/src/target/friend/compile_object.cpp) + +- `compiler`: Automatically chosen amongst ASM, C and C++ toolchain compiler +- `compile_flags`: Automatically chosen amongst `{c/cpp}_flags` +- `output`: Object file +- `input`: Input source file + +# Links Specific + +See [LinkTarget::CacheLinkCommand API](../../buildcc/lib/target/src/target/friend/link_target.cpp) + +- `output`: Generated target as `Target::GetName()` +- `compiled_sources`: Aggregated object files +- `lib_deps`: External libraries and full path libraries diff --git a/doc/user/installation_using_cmake.md b/doc/user/installation_using_cmake.md new file mode 100644 index 00000000..9e90bfbd --- /dev/null +++ b/doc/user/installation_using_cmake.md @@ -0,0 +1,69 @@ + +# Installation using CMake + +## BuildCC User options + +- BUILDCC_INSTALL: ON +- BUILDCC_BUILD_AS_SINGLE_LIB: ON + - Generates `libbuildcc` +- BUILDCC_BUILD_AS_INTERFACE_LIB: OFF + - Generates `libbuildcc_i` with other `lib`s linked during compilation +- BUILDCC_PRECOMPILE_HEADERS: OFF +- BUILDCC_EXAMPLES: OFF + - Uses SINGLE_LIB for its examples +- BUILDCC_TESTING: ON + - Unit testing with `ctest --output-on-failure` + - Only active for GCC compilers + - Provides code coverage + - `cmake --build {builddir} --target lcov_coverage` (on linux ONLY) + - `cmake --build {builddir} --target gcovr_coverage` (installed via pip gcovr) +- BUILDCC_CLANGTIDY: ON + - Auto runs with CMake +- BUILDCC_CPPCHECK: ON + - Cppcheck with `cmake --build {builddir} --target cppcheck_static_analysis` +- BUILDCC_DOCUMENTATION: ON + - Basic Doxygen generated html pages + - `cmake --build {builddir} --target doxygen_documentation` +- BUILDCC_NO_DEPRECATED: OFF + - Required on certain clang arch compilers `-Wno-deprecated` flag + +## Build + +> NOTE: Currently, BuildCC needs to be built from source and bootstrapped using CMake. + +> I aim to bootstrap BuildCC into an executable to remove the dependency on CMake. + +- By default all the developer options are turned OFF. +- Only the `BUILDCC_INSTALL` option is turned on. + +```bash +# Generate your project +cmake -B [Build folder] -G [Generator] +cmake -B build -G Ninja + +# Build your project +cmake --build build +``` + +## Install + +```bash +# Manually +cd [build_folder] +sudo cmake --install . + +# Cpack generators +cpack --help + +# ZIP +cpack -G ZIP + +# Executable +cpack -G NSIS +``` + +> NOTE: On windows [NSIS](https://nsis.sourceforge.io/Main_Page) needs to be installed + +- Install the package and add to environment PATH +- As a starting point, go through the **gcc/AfterInstall** example and **Hybrid** examples +- For more details read the `examples` README to use buildcc in different situations diff --git a/docs/source/_plantuml/plantuml-1.2021.16.jar b/docs/source/_plantuml/plantuml-1.2021.16.jar new file mode 100644 index 00000000..20b3baee Binary files /dev/null and b/docs/source/_plantuml/plantuml-1.2021.16.jar differ diff --git a/docs/source/arch/cmake_boilerplate.rst b/docs/source/arch/cmake_boilerplate.rst new file mode 100644 index 00000000..9a882cea --- /dev/null +++ b/docs/source/arch/cmake_boilerplate.rst @@ -0,0 +1,140 @@ +CMake Boilerplate +================= + +.. code-block:: cmake + + if (${TESTING}) + # setup mocking + # setup testing executables + + # TODO, Add example + endif() + + if(${BUILDCC_BUILD_AS_SINGLE_LIB}) + # buildcc files as an aggregate to one CMake library + # third party libraries still remain seperate so do NOT add it here + # Add third party library dependency to `buildcc` library in `buildcc/CMakeLists.txt` + + # TODO, Add example + endif() + + if(${BUILDCC_BUILD_AS_INTERFACE}) + # one buildcc library broken up into smaller library chunks instead of aggregated to one CMake library like in BUILDCC_BUILD_AS_SINGLE_LIB + # NOTE: Do not forget to add this small library chunk to `buildcc_i` library in `buildcc/CMakeLists.txt` + + # TODO, Add example + endif() + + if (${BUILDCC_INSTALL}) + # Install behaviour when option selected + + # TODO, Add example + endif() + + +When structuring our code we would like to create different folders with ``CMakeLists.txt`` files as individual compile units. +We can then ``add_subdirectory`` that particular folder. This helps us keep our codebase modular. + + +**Example: Environment** + +.. code-block:: cmake + + # Env test + if (${TESTING}) + add_library(mock_env STATIC + mock/logging.cpp + mock/assert_fatal.cpp + + src/env.cpp + src/task_state.cpp + + src/command.cpp + mock/execute.cpp + ) + target_include_directories(mock_env PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/mock/include + ) + target_link_libraries(mock_env PUBLIC + fmt::fmt + Taskflow + + CppUTest + CppUTestExt + gcov + ) + target_compile_options(mock_env PUBLIC ${TEST_COMPILE_FLAGS} ${BUILD_COMPILE_FLAGS}) + target_link_options(mock_env PUBLIC ${TEST_LINK_FLAGS} ${BUILD_LINK_FLAGS}) + + # Tests + add_executable(test_env_util test/test_env_util.cpp) + target_link_libraries(test_env_util PRIVATE mock_env) + + add_executable(test_task_state test/test_task_state.cpp) + target_link_libraries(test_task_state PRIVATE mock_env) + + add_executable(test_command test/test_command.cpp) + target_link_libraries(test_command PRIVATE mock_env) + + add_test(NAME test_env_util COMMAND test_env_util) + add_test(NAME test_task_state COMMAND test_task_state) + add_test(NAME test_command COMMAND test_command) + endif() + + set(ENV_SRCS + src/env.cpp + src/assert_fatal.cpp + src/logging.cpp + include/env/assert_fatal.h + include/env/env.h + include/env/logging.h + include/env/util.h + + include/env/host_os.h + include/env/host_compiler.h + include/env/host_os_util.h + + src/task_state.cpp + include/env/task_state.h + + src/command.cpp + src/execute.cpp + include/env/command.h + ) + + if(${BUILDCC_BUILD_AS_SINGLE_LIB}) + target_sources(buildcc PRIVATE + ${ENV_SRCS} + ) + target_include_directories(buildcc PUBLIC + $ + $ + ) + endif() + + if(${BUILDCC_BUILD_AS_INTERFACE}) + m_clangtidy("env") + add_library(env + ${ENV_SRCS} + ) + target_include_directories(env PUBLIC + $ + $ + ) + target_link_libraries(env PUBLIC fmt::fmt) + target_compile_options(env PRIVATE ${BUILD_COMPILE_FLAGS}) + target_link_options(env PRIVATE ${BUILD_LINK_FLAGS}) + target_link_libraries(env PRIVATE + spdlog::spdlog + tiny-process-library::tiny-process-library + ) + endif() + + if (${BUILDCC_INSTALL}) + if (${BUILDCC_BUILD_AS_INTERFACE}) + install(TARGETS env DESTINATION lib EXPORT envConfig) + install(EXPORT envConfig DESTINATION "${BUILDCC_INSTALL_LIB_PREFIX}/env") + endif() + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION "${BUILDCC_INSTALL_HEADER_PREFIX}") + endif() diff --git a/docs/source/arch/design_patterns.rst b/docs/source/arch/design_patterns.rst new file mode 100644 index 00000000..a8075385 --- /dev/null +++ b/docs/source/arch/design_patterns.rst @@ -0,0 +1,45 @@ +Design Patterns +=============== + +CRTP / Mixins +-------------- + +`Article by fluentcpp.com on CRTP `_ + +* Mixins are a design pattern used to add additional functionality to an existing class +* In this case, the ``TargetInfo`` and the ``Target`` class are meant to have a lot of setter APIs for user inputs. +* Adding more APIs to the class makes its difficult to read and maintain. +* For reference: See :doc:`serialization_schema` +* For example: In Target ``source_files`` have currently have several APIs + * ``AddSource`` + * ``AddSourceAbsolute`` + * ``GlobSources`` + * ``GlobSourcesAbsolute`` +* In the future we might have additional APIs such as + * ``AddSources`` that takes an ``std::initializer_list`` + * ``AddSources`` that takes a ``std::vector<>`` or ``std::unordered_set<>`` + * ``AddSource(s)ByPattern`` that takes a fmt string like ``{dir}/source.cpp`` +* From our serialization schema we can see that each of these fields could have many APIs associated with them. Having 50+ APIs for different fields in a single header i.e ``target_info.h`` / ``target.h`` would not be readible and hard to maintain. +* The Mixin / CRTP pattern is used to easily add functionality for a particular field in its own header / source file. + +Friend classes +--------------- + +* Friend classes are used when 2 classes have strong coupling with each other, but still need to maintain flexibility for other purposes. For example: Unit testing. +* In ``target.h`` we have 3 friend classes (non CRTP based) + * ``CompilePch`` + * ``CompileObject`` + * ``LinkTarget`` +* These 3 classes are made friend classes for 2 main purposes + * Unit Testing + * Flexibility / Maintaibility +* Unit Testing + * If these were not friend classes, the functions would've been private in scope within the ``Target`` class + * Unit testing these individual private functions would not be possible would public interfaces + * By making them friend classes, We can now unit test the public functions and embed this class in a private context with the ``Target`` class +* Flexibility / Maintaibility + * Each one of the classes mentioned above have their own information / states / tasks to hold. + * Without this segregation all of the member variables, states and tasks would need to be present inside the ``Target`` class +* Strong Coupling + * The 3 friend classes have strong coupling with the ``Target`` class since it uses its internal member variables for setting / getting information. + * The friend class can interact with the parent class and vice versa. diff --git a/docs/source/arch/namespaces.rst b/docs/source/arch/namespaces.rst new file mode 100644 index 00000000..7e55af10 --- /dev/null +++ b/docs/source/arch/namespaces.rst @@ -0,0 +1,18 @@ +Namespaces +========== + +User +----- + +* ``buildcc`` +* ``buildcc::env`` +* ``buildcc::plugin`` + +.. admonition:: User/Developer opinion + + Do we need to segregate the **Environment** into its own ``buildcc::env`` namespace or merge all those APIs into the ``buildcc`` namespace? + +Developer +---------- + +* ``buildcc::internal`` diff --git a/docs/source/arch/outputs.rst b/docs/source/arch/outputs.rst new file mode 100644 index 00000000..92273f35 --- /dev/null +++ b/docs/source/arch/outputs.rst @@ -0,0 +1,63 @@ +Outputs +======= + +BuildCC Library +----------------- + +The ``build_in_cpp`` project aims to remove DSLs by writing build files in C++. + +However our C++ "script" first needs to be **built (compiled and linked)** then **executed (build targets)**. + +When building (compiling and linking) our C++ "script" we need to link the ``buildcc`` library as well to provide the "script" with ``buildcc`` APIs. + +BuildExe Executable +--------------------- + +``BuildExe`` is a standalone executable similar to ``make.exe``. + +As part of the current release ``BuildExe`` requires the following folder structure. + +.. note:: Proper details of ``BuildExe`` usage to be added. For now this is in its experimental stage with only support for GCC, MinGW and MSVC compilers. + +.. uml:: + + @startmindmap + * ENV[BUILDCC_HOME] + ** buildcc + ** extensions + ** libs + ** host + @endmindmap + +ENV[BUILDCC_HOME] +^^^^^^^^^^^^^^^^^^ + +Create your BUILDCC_HOME directory. For example: ``C:/BUILDCCHOME`` or ``local/mnt/BUILDCCHOME`` and add this path to the **environment variable BUILDCC_HOME**. + +buildcc +^^^^^^^^ + +This directory contains the git cloned ``build_in_cpp`` repository + +extensions +^^^^^^^^^^^ + +This directory contains several git cloned second and third party plugins and extensions + +.. note:: BuildExe will have the ability to directly use these extensions after specifying them in the .toml file + +libs +^^^^^ + +This directory contains several git cloned libraries that need to be used in your projects or code base once they are specified in your .toml file. + +In this way, the ``build_in_cpp`` project automatically behaves like a package manager. + +host +^^^^^ + +Host toolchain toml files that contain information pertaining to the various host toolchains installed on your operating system. + +.. note:: The goal is to have a standalone executable to detect the host toolchains and automatically generate .toml files for each one of them. Users can then use these host toolchain files for building their "script" + +.. note:: Consider adding a **cross** folder for cross compiled toolchains are well which cannot be used to build the "script" but instead can be used by the script to build cross compiled targets. diff --git a/docs/source/arch/project_layout.rst b/docs/source/arch/project_layout.rst new file mode 100644 index 00000000..a62579f6 --- /dev/null +++ b/docs/source/arch/project_layout.rst @@ -0,0 +1,153 @@ +Project Layout +============== + +.. uml:: + + @startmindmap + * [root] + ** .clang-format + ** .gitmodules + ** CMakeLists.txt + ** CMakePresets.json + ** codecov.yml + ** LICENSE + ** README.md + ** TODO.md + * buildcc + ** lib + *** env + *** toolchain + *** target + *** args + *** register + ** schema + ** toolchains + ** targets + * bootstrap + * buildexe + * cmake + ** coverage + ** flags + ** target + ** tool + * docs + * example + ** gcc + ** msvc + ** hybrid + * third_party + ** CLI11 + ** cpputest + ** json + ** fmt + ** spdlog + ** taskflow + ** tiny-process-library + @endmindmap + +[workspace root] +---------------- + +* .clang-format + * LLVM style clang format +* .gitmodules + * See ``third_party`` folder for git submodules +* CMakeLists.txt + * Main project level CMakeLists + * Contains all the configurable options for CMake +* CMakePresets.json + * Default configurable options for different presets / compilers +* codecov.yml + * Codecov parameters +* LICENSE + * Apache-2.0 License +* README.md + * Project outline and details +* TODO.md + * TODO list for community interactions + +bootstrap +--------- + +Bootstrap build files to compile the ``build_in_cpp`` project + +* Contains build files for compiling ``third_party`` libraries + * Most of them are header only + * ``tiny-process-library`` is the only library that compiles to a static / dynamic library +* Contains build file for ``buildcc`` library compilation + +buildcc +-------- + +Contains the core ``buildcc`` library + +* schema +* lib + * env + * toolchain + * target + * args +* targets +* toolchains +* plugins + +buildexe +--------- + +Standalone ``buildcc`` executable for compiling projects in immediate mode or script mode + + +cmake +------- + +Global cmake variables and custom_targets + +* coverage + * gcovr.cmake + * lcov.cmake +* flags + * build_flags.cmake + * test_flags.cmake +* target + * json.cmake + * fmt.cmake + * spdlog.cmake + * tpl.cmake + * taskflow.cmake + * cli11.cmake + * cpputest.cmake +* tool + * clangtidy.cmake + * cppcheck.cmake + * doxygen.cmake + +docs +----- + +Project documentation + +* arch + * Project / Software architecture documentation +* user_api + * User usable APIs + +example +--------- + +* gcc + * Lowlevel tests for the GCC compiler +* msvc + * Lowlevel tests for the MSVC compiler +* hybrid + * Real world usages with both GCC and MSVC compiler support + +third_party +----------- + +* JSON +* Fmtlib +* Spdlog +* CLI11 +* Taskflow +* Tiny Process Library +* CppUTest diff --git a/docs/source/arch/serialization_schema.rst b/docs/source/arch/serialization_schema.rst new file mode 100644 index 00000000..28223b81 --- /dev/null +++ b/docs/source/arch/serialization_schema.rst @@ -0,0 +1,153 @@ +Serialization Schema +==================== + +Path +----- + +.. code-block:: none + + namespace schema.internal; + + table Path { + pathname:string (key); + last_write_timestamp:uint64; + } + +* ``Path`` is used when we want to verify the physical presence of a particular file +* The ``last_write_timestamp`` is used to check if we need to rebuild the file. +* However we can use different rebuild strategies in the future. Ex: ``last_write_timestamp:uint64`` can be converted to ``hash:string`` + +Generator +--------- + +.. code-block:: none + + namespace schema.internal; + + // Each generator consists of many relational files of [input] - [output] - [commands] + table Generator { + name:string (key); + inputs:[Path]; + outputs:[string]; + commands:[string]; + } + root_type Generator; + +.. uml:: + + start + :Inputs; + :Commands; + :Outputs; + stop + + + +Target +------- + +.. code-block:: none + + namespace schema.internal; + + enum TargetType : byte { + Executable, + StaticLibrary, + DynamicLibrary + } + + // TODO, Check if Toolchain needs to be added to Target + table Target { + // Metadata + name:string (key); + type:TargetType; + + // Input + // Files + source_files:[Path]; + header_files:[Path]; + pch_files:[Path]; + lib_deps:[Path]; + + // Links + external_lib_deps:[string]; + + // Directories + include_dirs:[string]; + lib_dirs:[string]; + + // Flags + preprocessor_flags:[string]; + common_compile_flags:[string]; + pch_compile_flags:[string]; + pch_object_flags:[string]; + asm_compile_flags:[string]; + c_compile_flags:[string]; + cpp_compile_flags:[string]; + link_flags:[string]; + + // Additional dependencies + compile_dependencies:[Path]; + link_dependencies:[Path]; + + // Output + // Does not need to be stored + + // State + pch_compiled:bool; + target_linked:bool; + } + root_type Target; + + +.. uml:: + + start + + split + :SourceFiles; + + split again + :HeaderFiles; + + split again + :PchFiles; + + split again + :IncludeDirs; + + split again + :PreprocessorFlags; + + split again + :CompileFlags; + + split again + :CompileDependencies; + end split + + :Compile; + + split + :Objects; + + split again + :LibDeps; + + split again + :ExternalLibDeps; + + split again + :LibDirs; + + split again + :LinkFlags; + + split again + :LinkDependencies; + end split + + :Link; + + :Target; + stop diff --git a/docs/source/arch/software_heirarchy.rst b/docs/source/arch/software_heirarchy.rst new file mode 100644 index 00000000..feb4540e --- /dev/null +++ b/docs/source/arch/software_heirarchy.rst @@ -0,0 +1,87 @@ +Software Heirarchy +================== + +BuildCC single lib +------------------- + +**BuildCC** sources are compiled into a single library + +* The easiest way to use ``BuildCC`` +* After building the project all we need to do is ``-lbuildcc -ltiny-process-library`` or equivalent + +.. uml:: + + rectangle JSON as json + rectangle fmt as fmt + rectangle spdlog as spdlog + rectangle Taskflow as taskflow + rectangle CLI11 as cli11 + rectangle "tiny-process-library" as tpl + + rectangle BuildCC as buildcc + + json -up-> buildcc + fmt -up-> buildcc + spdlog -up-> buildcc + taskflow -up-> buildcc + cli11 -up-> buildcc + tpl -up-> buildcc + + +BuildCC interface lib +--------------------- + +**BuildCC** is broken up into multiple smaller libraries + +* This has been done mainly for unit-testing and mocking segregation +* It helps to easily architect the ``BuildCC`` library by visualizing internal dependencies +* Please see :doc:`testing` for more information of how the ``mock_*`` equivalent of these libraries are used + +.. uml:: + + rectangle JSON as json #palegreen + rectangle fmt as fmt #palegreen + rectangle spdlog as spdlog #palegreen + rectangle Taskflow as taskflow #palegreen + rectangle CLI11 as cli11 #palegreen + rectangle "tiny-process-library" as tpl #palegreen + + rectangle Environment as env #aliceblue + rectangle Schema as schema #aliceblue + rectangle Toolchain as toolchain #aliceblue + rectangle Target as target #aliceblue + rectangle "Toolchain specialized" as toolchain_specialized #aliceblue + rectangle "Target specialized" as target_specialized #aliceblue + rectangle Args as args #aliceblue + rectangle Register as register #aliceblue + rectangle "Supported Plugins" as plugins #aliceblue + rectangle BuildCC as buildcc + + + fmt -up-> env + spdlog .up.> env + tpl .up.> env + + cli11 -up-> args + taskflow -up-> register + + json .up.> schema + env -up-> schema + + schema -up-> toolchain + + toolchain -up-> target + taskflow -up-> target + + toolchain -up-> toolchain_specialized + target -up-> target_specialized + + target -up-> args + target -up-> register + target -up-> plugins + + toolchain_specialized -up-> buildcc + target_specialized -up-> buildcc + args -up-> buildcc + register -up-> buildcc + plugins -up-> buildcc diff --git a/docs/source/arch/style_guide.rst b/docs/source/arch/style_guide.rst new file mode 100644 index 00000000..a27f05e2 --- /dev/null +++ b/docs/source/arch/style_guide.rst @@ -0,0 +1,22 @@ +Style Guide +============ + +Defaults +-------- + +``PascalCase`` for struct, classes and member functions (inside classes and structs) + +``snake_case`` for free functions and local function variables + +Google Style +------------ + +``[snake_case_variable_name]`` for public member variables + +``[snake_case_variable_name]_`` for private member variables + +``k[PascalCaseVariableName]`` constexpr variables and enum / enum classes names ONLY when used internally + +``[PascakCaseVariableName]`` constexpr variables and enum / enum classes names when exposed to users + +.. attention:: If you see any wrong usage in the project please raise an issue diff --git a/docs/source/arch/testing.rst b/docs/source/arch/testing.rst new file mode 100644 index 00000000..31423bef --- /dev/null +++ b/docs/source/arch/testing.rst @@ -0,0 +1,139 @@ +Testing +======= + +Test libs +---------- + +* ``mock_env`` +* ``mock_toolchain`` +* ``mock_target`` + +mock_env +^^^^^^^^ + +* ``assert_handle_fatal`` uses ``throw std::exception`` instead of ``std::terminate`` +* We can now use the CppUTest ``CHECK_THROWS`` API when a failure occurs + +.. doxygenfunction:: assert_handle_fatal + + +* ``Command::Execute`` is mocked out to CppUMock ``.actualCall("execute")`` API +* We can redirect stdout and stderr through the actual call, with our own information to match expectations using the CppUMock ``.withOutputParameterOfType`` API +* Provided with the ``CommandExpect_Execute`` mocked API +* The ``command`` and ``working_directory`` params are not used + +.. doxygenfunction:: CommandExpect_Execute + +* All of the logging APIs have been **stubbed** out since they are unnecessary for unit tests and very noisy when getting output +* A better alternative would be debugging unit tests or adding the CppUTest ``UT_PRINT`` statement instead + +.. doxygenclass:: buildcc::env::m::VectorStringCopier + +mock_toolchain +^^^^^^^^^^^^^^ + +* Does not override any classes / functions + +mock_target +^^^^^^^^^^^^ + +.. doxygenfunction:: CustomGeneratorRunner + +.. doxygenfunction:: CustomGeneratorExpect_IdRemoved + +.. doxygenfunction:: CustomGeneratorExpect_IdAdded + +.. doxygenfunction:: CustomGeneratorExpect_IdUpdated + +From the :doc:`serialization_schema` **generator** we can see that our generator has 3 rebuild conditions + +* Inputs + * Removed state (input file is removed) + * Added state (input file is added) + * Updated state (input file is updated) +* Outputs + * Changed (output file is added or removed) +* Commands + * Changed (command is added or removed) + +.. doxygenfunction:: TargetRunner + +.. doxygenfunction:: TargetExpect_SourceRemoved + +.. doxygenfunction:: TargetExpect_SourceAdded + +.. doxygenfunction:: TargetExpect_SourceUpdated + +.. doxygenfunction:: TargetExpect_PathRemoved + +.. doxygenfunction:: TargetExpect_PathAdded + +.. doxygenfunction:: TargetExpect_PathUpdated + +.. doxygenfunction:: TargetExpect_DirChanged + +.. doxygenfunction:: TargetExpect_FlagChanged + +.. doxygenfunction:: TargetExpect_ExternalLibChanged + +From the :doc:`serialization_schema` **target** we can see that our generator has multiple rebuild conditions + +Anything associated with ``Path`` has 3 states i.e Added, Removed or Updated + +Everything else has only 2 states i.e Added or Removed + +Tests +------ + +* ``env`` +* ``toolchain`` +* ``target`` +* ``args`` +* ``register`` +* ``plugins`` + +env +^^^^ + +* test_env_util +* test_task_state +* test_command + +toolchain +^^^^^^^^^^ + +* test_toolchain_verify + +target +^^^^^^^ + +* test_path +* test_builder_interface +* test_target_config +* test_target_state +* test_generator +* test_compile_object +* test_base_target +* test_target_pch +* test_target_source +* test_target_source_out_of_root +* test_target_include_dir +* test_target_lib_dep +* test_target_external_lib +* test_target_flags +* test_target_user_deps +* test_target_lock +* test_target_sync +* test_target_failure_states + +args +^^^^^ + +* test_args +* test_register +* test_persistent_storage + +plugins +^^^^^^^^ + +.. note:: Incomplete implementation and tests diff --git a/docs/source/arch/toc.rst b/docs/source/arch/toc.rst new file mode 100644 index 00000000..ebe70bdf --- /dev/null +++ b/docs/source/arch/toc.rst @@ -0,0 +1,14 @@ +Architecture +============= + +.. toctree:: + + project_layout + software_heirarchy + serialization_schema + cmake_boilerplate + namespaces + design_patterns + testing + outputs + style_guide diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 00000000..883d8a9e --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,64 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'BuildCC' +copyright = '2021-2022, Niket Naidu' +author = 'Niket Naidu' + +# The full version, including alpha/beta/rc tags +release = '0.1.1' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "breathe", + "sphinxcontrib.plantuml" +] + +breathe_default_project = "buildcc_documentation" +breathe_default_members = ("members", "undoc-members") + +print(f"Current Dir for plantuml jar file: {os.getcwd()}") +plantuml = f"java -jar {os.getcwd()}/_plantuml/plantuml-1.2021.16.jar" +# plantuml_output_format = "svg" #png / svg + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'furo' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/source/examples/clang.rst b/docs/source/examples/clang.rst new file mode 100644 index 00000000..0c207d46 --- /dev/null +++ b/docs/source/examples/clang.rst @@ -0,0 +1,4 @@ +Clang +====== + +Lowlevel Clang Tests diff --git a/docs/source/examples/gcc.rst b/docs/source/examples/gcc.rst new file mode 100644 index 00000000..fb19a1a4 --- /dev/null +++ b/docs/source/examples/gcc.rst @@ -0,0 +1,268 @@ +GCC +==== + +Lowlevel GCC Tests + +Simple +-------- + +Compile a single source + +.. code-block:: cpp + :linenos: + :emphasize-lines: 8 + + // GCC specialized toolchain + Toolchain_gcc toolchain; + + // GCC specialized targets + // Create "Simple" target (meant to use the GCC compiler) + // On Windows the equivalent is the MinGW compiler + ExecutableTarget_gcc target("Simple", gcc, ""); + target.AddSource("main.cpp"); + target.Build(); + + // Build + tf::Executor executor; + executor.run(target.GetTaskflow()); + executor.wait_for_all(); + + +IncludeDir +---------- + +Compile multiple sources with header files + +.. code-block:: cpp + :linenos: + :emphasize-lines: 12,15 + + // GCC specialized toolchain + Toolchain_gcc toolchain; + + // GCC specialized targets + // Create "IncludeDir" target (meant to use the GCC compiler) + // On Windows the equivalent is the MinGW compiler + ExecutableTarget_gcc target("IncludeDir", gcc, "files"); + target.AddSource("main.cpp", "src"); + target.AddSource("src/random.cpp"); + + // Track header for rebuilds + target.AddHeader("include/random.h"); + + // Add include dir to search paths + target.AddIncludeDir("include"); + target.Build(); + + // Build + tf::Executor executor; + executor.run(target.GetTaskflow()); + executor.wait_for_all(); + +StaticLib +---------- + +Compile a static library which is used by an executable + +.. code-block:: cpp + :linenos: + :emphasize-lines: 7 + + // GCC specialized toolchain + Toolchain_gcc toolchain; + + // GCC specialized targets + // Create "librandom.a" target (meant to use the GCC compiler) + // On Windows the equivalent is the MinGW compiler + StaticTarget_gcc statictarget("librandom", gcc, "files"); + statictarget.AddSource("src/random.cpp"); + statictarget.AddHeader("include/random.h"); + statictarget.AddIncludeDir("include"); + statictarget.Build(); + + // GCC specialized targets + // Create "statictest" target (meant to use the GCC compiler) + // On Windows the equivalent is the MinGW compiler + ExecutableTarget_gcc exetarget("statictest", gcc, "files"); + exetarget.AddSource("main.cpp", "src"); + exetarget.AddIncludeDir("include"); + exetarget.AddLibDep(statictarget); + exetarget.Build(); + + // Build + tf::Executor executor; + tf::Taskflow taskflow; + + // Explicitly setup your dependencies + tf::Task statictargetTask = taskflow.composed_of(statictarget.GetTaskflow()); + tf::Task exetargetTask = taskflow.composed_of(exetarget.GetTaskflow()); + exetargetTask.succeed(statictargetTask); + + // Run + executor.run(taskflow); + executor.wait_for_all(); + +DynamicLib +----------- + +Compile a dynamic library which is used by an executable + +.. code-block:: cpp + :linenos: + :emphasize-lines: 7 + + // GCC specialized toolchain + Toolchain_gcc toolchain; + + // GCC specialized targets + // Create "librandom.so" target (meant to use the GCC compiler) + // On Windows the equivalent is the MinGW compiler + DynamicTarget_gcc dynamictarget("librandom", gcc, "files"); + dynamictarget.AddSource("src/random.cpp"); + dynamictarget.AddHeader("include/random.h"); + dynamictarget.AddIncludeDir("include"); + dynamictarget.Build(); + + // GCC specialized targets + // Create "dynamictest" target (meant to use the GCC compiler) + // On Windows the equivalent is the MinGW compiler + ExecutableTarget_gcc target("dynamictest", gcc, "files"); + target.AddSource("main.cpp", "src"); + target.AddIncludeDir("include"); + target.AddLibDep(dynamictarget); + target.Build(); + + // Build + tf::Executor executor; + tf::Taskflow taskflow; + + // Explicitly setup your dependencies + auto dynamictargetTask = taskflow.composed_of(dynamictarget.GetTaskflow()); + auto targetTask = taskflow.composed_of(target.GetTaskflow()); + targetTask.succeed(dynamictargetTask); + + executor.run(taskflow); + executor.wait_for_all(); + + // Post Build step + if (target.IsBuilt()) { + fs::path copy_to_path = + target.GetTargetBuildDir() / dynamictarget.GetTargetPath().filename(); + fs::copy(dynamictarget.GetTargetPath(), copy_to_path); + } + +.. note:: Our ``ExecutableTarget_gcc`` depends on ``DynamicTarget_gcc`` and requires the ``librandom.so`` file to be present in the same folder location as the executable when running. + +Flags +------ + +Using **PreprocessorFlags**, **C Compile flags**, **Cpp Compile flags** and **Link flags** + +.. code-block:: cpp + :linenos: + :emphasize-lines: 12,13,14,15,23,24,25,26 + + // GCC specialized toolchain + Toolchain_gcc toolchain; + + // GCC specialized targets + // Create "CppFlags" target (meant to use the GCC compiler) + // On Windows the equivalent is the MinGW compiler + ExecutableTarget_gcc cpptarget("CppFlags", gcc, "files"); + cpptarget.AddSource("main.cpp", "src"); + cpptarget.AddSource("src/random.cpp"); + cpptarget.AddHeader("include/random.h"); + cpptarget.AddIncludeDir("include"); + cpptarget.AddPreprocessorFlag("-DRANDOM=1"); + cpptarget.AddCppCompileFlag("-Wall"); + cpptarget.AddCppCompileFlag("-Werror"); + cpptarget.AddLinkFlag("-lm"); + cpptarget.Build(); + + // Gcc specialized targets + // Create "CFlags" target (meant to use the GCC compiler) + // On Windows the equivalent is the MinGW compiler + ExecutableTarget_gcc ctarget("CFlags", gcc, "files"); + ctarget.AddSource("main.c", "src"); + ctarget.AddPreprocessorFlag("-DRANDOM=1"); + ctarget.AddCCompileFlag("-Wall"); + ctarget.AddCCompileFlag("-Werror"); + ctarget.AddLinkFlag("-lm"); + ctarget.Build(); + + // Build + tf::Executor executor; + tf::Taskflow taskflow; + + // There isn't any dependency between the 2 targets + taskflow.composed_of(cpptarget.GetTaskflow()); + taskflow.composed_of(ctarget.GetTaskflow()); + + executor.run(taskflow); + executor.wait_for_all(); + +AfterInstall +------------- + +Use BuildCC with CMake + + +* Install ``BuildCC`` via CMake to your system and add it to **PATH** +* Use the script below to **compile** your build script to an executable +* Copy the **Flags** build example +* Run the executable from your project root directory + +.. code-block:: cmake + + # Package dependencies + # fmt is imported by spdlog by default + find_package(fmt_package NAMES "fmt" REQUIRED) + find_package(spdlog_package NAMES "spdlog" REQUIRED) + find_package(nlohmann_json_package NAMES "nlohmann_json" REQUIRED) + find_package(taskflow_package NAMES "Taskflow" "taskflow" REQUIRED) + find_package(CLI11_package NAMES "CLI11" REQUIRED) + find_package(tiny_process_library_package NAMES "tiny-process-library" REQUIRED) + + find_package(buildcc_package NAMES "buildcc" REQUIRED) + + message("Find package: ${fmt_package_DIR}") + message("Find package: ${spdlog_package_DIR}") + message("Find package: ${nlohmann_json_package_DIR}") + message("Find package: ${taskflow_package_DIR}") + message("Find package: ${CLI11_package_DIR}") + message("Find package: ${tiny_process_library_package_DIR}") # + + message("Find package: ${buildcc_package_DIR}") + + # build executable + add_executable(build build.cpp) + target_include_directories(build PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated) + target_link_libraries(build PRIVATE + buildcc + ) + if (${MINGW}) + message(WARNING "-Wl,--allow-multiple-definition for MINGW") + target_link_options(build PRIVATE -Wl,--allow-multiple-definition) + endif() + + # Add your constants file for the environment + configure_file(constants.h.in ${CMAKE_BINARY_DIR}/generated/constants.h @ONLY) + + +Plugins +-------- + +Demonstrating BuildCC supported plugin usage + +* From the **Flags** example above +* Add the targets for which you would like to generate the `Clang CompileCommands Database `_ + +.. code-block:: cpp + :linenos: + + plugin::ClangCompileCommands({&cppflags, &cflags}).Generate(); + +PrecompileHeader +---------------- + +TODO diff --git a/docs/source/examples/hybrid.rst b/docs/source/examples/hybrid.rst new file mode 100644 index 00000000..b79b802d --- /dev/null +++ b/docs/source/examples/hybrid.rst @@ -0,0 +1,562 @@ +Hybrid +======= + +Real world tests combining multiple compilers + +.. note:: In the Hybrid examples we use the **Args** and **Register** module to abstract away ``Taskflow`` and ``CLI11`` APIs. To see the lowlevel usage please see the lowlevel **[compiler]** tests. + +Single Boilerplate +------------------- + +We are only using a single Toolchain - Target pair + +.. code-block:: cpp + :linenos: + + int main(int argc, char ** argv) { + // Register your [toolchain.{name}] + // In this case it will be [toolchain.gcc] + ArgToolchain arg_gcc; + + // Args module to get data from the command line or .toml file passed in through --config + Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc) + .Parse(argc, argv); + + // Register module + Reg::Init(); + Reg::Call(Args::Clean()).Func(clean_cb); + + // TODO, Write your target builds here + // See examples below + } + + static void clean_cb() { + env::log_info(EXE, fmt::format("Cleaning {}", env::get_project_build_dir())); + fs::remove_all(env::get_project_build_dir()); + } + + +.. code-block:: toml + :linenos: + + # Required parameters + root_dir = "" # build executable meant to be invoked from the current directory + build_dir = "_build" # Creates this directory relative to where you invoke your build executable + + # Optional parameters + loglevel = "trace" # trace, debug, info, warning, critical + clean = true # calls clean_cb if true, user specifies how their project must be cleaned + + # Toolchain + # Valid configurations are + # build = false, test = false + # build = true, test = false + # build = true, test = true + [toolchain.gcc] + build = true + test = true + + +Single +------- + +Compile a single source with a single GCC compiler. + +.. code-block:: cpp + :linenos: + + int main(int argc, char ** argv) { + // See Single Boilerplate + + Toolchain_gcc gcc; + ExecutableTarget_gcc hello_world("hello_world", gcc, ""); + + // Select your builds and tests using the .toml files + Reg::Toolchain(arg_gcc.state) + .Func([&]() { gcc.Verify(); }) + .Build(arg_gcc.state, hello_world_build_cb, hello_world) + .Test(arg_gcc.state, "{executable}", hello_world); + + // Build and Test Target + Reg::Run(); + } + + static void hello_world_build_cb(BaseTarget &target) { + target.AddSource("main.cpp", "src"); + target.Build(); + } + + +Multiple Boilerplate +----------------------- + +We are using multiple Toolchain - Target pairs + +.. code-block:: cpp + :linenos: + + int main(int argc, char ** argv) { + // Register your [toolchain.{name}] + // In this case it will be [toolchain.gcc] and [toolchain.msvc] + ArgToolchain arg_gcc; + ArgToolchain arg_msvc; + + // Args module to get data from the command line or .toml file passed in through --config + Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc) + .AddToolchain("msvc", "Generic msvc toolchain", arg_msvc) + .Parse(argc, argv); + // NOTE, You can add more toolchains here as per your project requirement + + // Register module + Reg::Init(); + Reg::Call(Args::Clean()).Func(clean_cb); + + // TODO, Write your target builds here + // See examples below + } + + static void clean_cb() { + env::log_info(EXE, fmt::format("Cleaning {}", env::get_project_build_dir())); + fs::remove_all(env::get_project_build_dir()); + } + + +.. code-block:: toml + :linenos: + + # Required parameters + root_dir = "" # build executable meant to be invoked from the current directory + build_dir = "_build" # Creates this directory relative to where you invoke your build executable + + # Optional parameters + loglevel = "trace" # trace, debug, info, warning, critical + clean = true # calls clean_cb if true, user specifies how their project must be cleaned + + # Toolchain + # Valid configurations are + # build = false, test = false + # build = true, test = false + # build = true, test = true + [toolchain.gcc] + build = true + test = true + + # If we are building on Windows make these true + [toolchain.msvc] + build = false + test = false + +.. note:: On Windows, make sure you install the Build Tools properly and invoke ``vcvarsall.bat amd64`` or equivalent from the command line to activate your toolchain. + +Simple +------- + +Similar to lowlevel GCC Flags example for both the GCC and MSVC compiler + +.. code-block:: cpp + :linenos: + + int main(int argc, char ** argv) { + // See Multiple Boilerplate + + Toolchain_gcc gcc; + ExecutableTarget_gcc g_cppflags("cppflags", gcc, "files"); + ExecutableTarget_gcc g_cflags("cflags", gcc, "files"); + // Select your builds and tests using the .toml files + Reg::Toolchain(arg_gcc.state) + .Func([&](){ gcc.Verify(); }) + .Build(cppflags_build_cb, g_cppflags) + .Build(cflags_build_cb, g_cflags) + .Test("{executable}", g_cppflags) + .Test("{executable}", g_cflags); + + Toolchain_msvc msvc; + ExecutableTarget_msvc m_cppflags("cppflags", msvc, "files"); + ExecutableTarget_msvc m_cflags("cflags", msvc, "files"); + Reg::Toolchain(arg_msvc.state) + .Func([&](){ msvc.Verify(); }) + .Build(cppflags_build_cb, m_cppflags) + .Build(cflags_build_cb, m_cflags) + .Test("{executable}", m_cppflags) + .Test("{executable}", m_cflags); + + // Build and Test target + Reg::Run(); + + return 0; + } + + static void cppflags_build_cb(BaseTarget &cppflags) { + cppflags.AddSource("main.cpp", "src"); + cppflags.AddSource("src/random.cpp"); + cppflags.AddIncludeDir("include", true); + + // Toolchain specific code goes here + switch (cppflags.GetToolchain().GetId()) { + case ToolchainId::Gcc: { + cppflags.AddPreprocessorFlag("-DRANDOM=1"); + cppflags.AddCppCompileFlag("-Wall"); + cppflags.AddCppCompileFlag("-Werror"); + cppflags.AddLinkFlag("-lm"); + break; + } + case ToolchainId::Msvc: { + cppflags.AddPreprocessorFlag("/DRANDOM=1"); + cppflags.AddCppCompileFlag("/W4"); + break; + } + default: + break; + } + + cppflags.Build(); + } + + static void cflags_build_cb(BaseTarget &cflags) { + cflags.AddSource("main.c", "src"); + + // Toolchain specific code goes here + switch (cflags.GetToolchain().GetId()) { + case ToolchainId::Gcc: { + cflags.AddPreprocessorFlag("-DRANDOM=1"); + cflags.AddCCompileFlag("-Wall"); + cflags.AddCCompileFlag("-Werror"); + cflags.AddLinkFlag("-lm"); + break; + } + case ToolchainId::Msvc: { + cflags.AddPreprocessorFlag("/DRANDOM=1"); + cflags.AddCCompileFlag("/W4"); + break; + } + default: + break; + } + + cflags.Build(); + } + +Foolib +------- + +For library developers + +.. admonition:: Scenario + + Say suppose you are a library developer who has created an amazing ``Foo`` library. How would you easily specifiy your project build to be used by yourself and end users? + + **Solution**: Create Header / Source segregations. For example. ``build.foo.h`` and ``build.foo.cpp`` + End users can now create their own ``build.[project].cpp`` file and compile ``build.foo.cpp`` along with their source and use appropriate APIs are provided by your files. + + Depending on the complexity of your project the library developer can provide multiple APIs with different options that need to be selected at run time / compile time. + +**Header** + +.. code-block:: cpp + :linenos: + + #pragma once + + #include "buildcc.h" + + void fooTarget(buildcc::BaseTarget &target, const fs::path &relative_path); + +**Source** + +.. code-block:: cpp + :linenos: + + #include "build.foo.h" + + void fooTarget(buildcc::BaseTarget &target, const fs::path &relative_path) { + target.AddSource(relative_path / "src/foo.cpp"); + target.AddIncludeDir(relative_path / "src", true); + } + + +External Lib +------------- + +For end users consuming third party libraries + +.. admonition:: Scenario + + User would like to use the third party library ``Foo`` in their codebase. The ``Foo`` library resides in a different directory as visualized below. + +.. uml:: + + @startmindmap + * [folder] + ** external_lib + *** [project_root] + ** foolib + @endmindmap + +.. code-block:: cpp + :linenos: + + #include "build.foo.h" + + int main(int argc, char ** argv) { + // See Multiple Boilerplate + + // Build steps + Toolchain_gcc gcc; + Toolchain_msvc msvc; + + ExecutableTarget_gcc g_foolib("foolib", gcc, TargetEnv("...")); + ExecutableTarget_msvc m_foolib("foolib", msvc, TargetEnv("..."); + + Reg::Toolchain(arg_gcc.state) + .Build(foolib_build_cb, g_foolib); + + Reg::Toolchain(arg_msvc.state) + .Build(foolib_build_cb, m_foolib); + + Reg::Run(); + } + + static void foolib_build_cb(BaseTarget &target) { + fooTarget(target, "../foolib"); + target.AddSource("main.cpp"); + target.Build(); + } + +Custom Target +---------------- + +For super customized targets and toolchains + +.. code-block:: cpp + :linenos: + + int main(int argc, char ** argv) { + + ArgToolchain arg_gcc; + ArgToolchain arg_msvc; + ArgToolchain toolchain_clang_gnu; + ArgTarget target_clang_gnu; + + Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc) + .AddToolchain("msvc", "Generic msvc toolchain", arg_msvc) + .AddToolchain("clang_gnu", "Clang GNU toolchain", toolchain_clang_gnu) + .AddTarget("clang_gnu", "Clang GNU target", target_clang_gnu) + .Parse(argc, argv); + + // Additional boilerplate + + // Supplied at compile time + Toolchain_gcc gcc; + Toolchain_msvc msvc; + // Get custom toolchain from the command line, supplied at run time + auto &clang = toolchain_clang_gnu; + clang.SetToolchainInfoFunc(GlobalToolchainInfo::Get(clang.id)); + + ExecutableTarget_gcc g_foolib("foolib", gcc, ""); + ExecutableTarget_msvc m_foolib("foolib", msvc, ""); + // Get compile_command and link_command from the command line + Target_custom c_foolib("CFoolib.exe", TargetType::Executable, clang, "", target_clang_gnu.GetTargetConfig()); + + Reg::Toolchain(arg_gcc.state) + .Build(foolib_build_cb, g_foolib); + Reg::Toolchain(arg_msvc.state) + .Build(foolib_build_cb, m_foolib); + Reg::Toolchain(toolchain_clang_gnu.state) + .Build( foolib_build_cb, c_foolib); + + // Build targets + Reg::Run(); + } + + static void foolib_build_cb(BaseTarget &target) { + target.AddSource("src/foo.cpp"); + target.AddIncludeDir("src", true); + target.AddSource("main.cpp"); + target.Build(); + } + +.. code-block:: toml + :linenos: + + # See Multiple boilerplate .toml file + + # Custom toolchain added here + [toolchain.clang_gnu] + build = true + test = true + + # Custom toolchain added here, supplied during runtime + id = "Clang" + name = "clang_gnu" + asm_compiler = "llvm-as" + c_compiler = "clang" + cpp_compiler = "clang++" + archiver = "llvm-ar" + linker = "ld" + + # Custom target added here + [target.clang_gnu] + compile_command = "{compiler} {preprocessor_flags} {include_dirs} {compile_flags} -o {output} -c {input}" + link_command = "{cpp_compiler} {link_flags} {compiled_sources} -o {output} {lib_dirs} {lib_deps}" + + +PrecompileHeader +---------------- + +Precompile header usage with GCC and MSVC compilers + +.. code-block:: cpp + :linenos: + + // Modified Lowlevel GCC Flags example for PCH + + static void cppflags_build_cb(BaseTarget &cppflags) { + cppflags.AddSource("main.cpp", "src"); + cppflags.AddSource("random.cpp", "src"); + cppflags.AddIncludeDir("include", true); + + cppflags.AddPch("pch/pch_cpp.h"); + cppflags.AddPch("pch/pch_c.h"); + cppflags.AddIncludeDir("pch", true); + + // Toolchain specific code goes here + switch (cppflags.GetToolchain().GetId()) { + case ToolchainId::Gcc: { + cppflags.AddPreprocessorFlag("-DRANDOM=1"); + cppflags.AddCppCompileFlag("-Wall"); + cppflags.AddCppCompileFlag("-Werror"); + cppflags.AddLinkFlag("-lm"); + break; + } + case ToolchainId::Msvc: { + cppflags.AddPreprocessorFlag("/DRANDOM=1"); + cppflags.AddCppCompileFlag("/W4"); + break; + } + default: + break; + } + + cppflags.Build(); + } + + static void cflags_build_cb(BaseTarget &cflags) { + cflags.AddSource("main.c", "src"); + + cflags.AddPch("pch/pch_c.h"); + cflags.AddIncludeDir("pch", false); + cflags.AddHeader("pch/pch_c.h"); + + // Toolchain specific code goes here + switch (cflags.GetToolchain().GetId()) { + case ToolchainId::Gcc: { + cflags.AddPreprocessorFlag("-DRANDOM=1"); + cflags.AddCCompileFlag("-Wall"); + cflags.AddCCompileFlag("-Werror"); + cflags.AddLinkFlag("-lm"); + break; + } + case ToolchainId::Msvc: { + cflags.AddPreprocessorFlag("/DRANDOM=1"); + cflags.AddCCompileFlag("/W4"); + break; + } + default: + break; + } + + cflags.Build(); + } + +Dependency Chaining +--------------------- + +Chain **Generators** and **Targets** using the ``Register`` module + +.. code-block:: cpp + :linenos: + + int main(int argc, char ** argv) { + // See Multiple Boilerplate + + Toolchain_gcc gcc; + Toolchain_msvc msvc; + + BaseGenerator cpp_generator("cpp_generator", ""); + Reg::Call().Build(cpp_generator_cb, cpp_generator); + + ExecutableTarget_gcc g_cpptarget("cpptarget", gcc, ""); + Reg::Toolchain(arg_gcc.state) + .Func([&](){ gcc.Verify(); }) + .Build(cpp_target_cb, g_cpptarget, cpp_generator) + .Dep(g_cpptarget, cpp_generator); + + ExecutableTarget_msvc m_cpptarget("cpptarget", msvc, ""); + Reg::Toolchain(arg_msvc.state) + .Func([&](){ msvc.Verify(); }) + .Build(cpp_target_cb, m_cpptarget, cpp_generator) + .Dep(m_cpptarget, cpp_generator); + } + + // Use a python generator to create main.cpp + static void cpp_generator_cb(BaseGenerator &generator) { + generator.AddOutput("{gen_build_dir}/main.cpp", "main_cpp"); + generator.AddCommand("python3 {gen_root_dir}/python/gen.py --source_type cpp " + "--destination {main_cpp}"); + generator.Build(); + } + + // Use main.cpp generated by the python script to compile your target + static void cpp_target_cb(BaseTarget &cpptarget, + const BaseGenerator &cpp_generator) { + const fs::path main_cpp = + fs::path(cpp_generator.GetValueByIdentifier("main_cpp")) + .lexically_relative(env::get_project_root_dir()); + cpptarget.AddSource(main_cpp); + cpptarget.Build(); + } + + +Target Info +------------- + +* Target Info usage to store Target specific information +* Example usage for Header Only targets, however it can store information for all Target inputs +* Common information used between multiple targets can be stored into a `TargetInfo` instance + +.. code-block:: cpp + :linenos: + + int main(int argc, char ** argv) { + // See Multiple boilerplate + + Toolchain_gcc gcc; + Toolchain_msvc msvc; + + // TargetInfo + TargetInfo g_genericadd_ho(gcc, "files"); + ExecutableTarget_gcc g_genericadd1("generic_add_1", gcc, "files"); + Reg::Toolchain(arg_gcc.state) + .Func(genericadd_ho_cb, g_genericadd_ho) + .Build(genericadd1_build_cb, g_genericadd1, g_genericadd_ho); + + TargetInfo m_genericadd_ho(msvc, "files"); + ExecutableTarget_msvc m_genericadd1("generic_add_1", msvc, "files"); + Reg::Toolchain(arg_msvc.state) + .Func(genericadd_ho_cb, m_genericadd_ho) + .Build(genericadd1_build_cb, m_genericadd1, m_genericadd_ho); + } + + // HO library contains include dirs and header files which are copied into executable target + static void genericadd1_build_cb(BaseTarget &genericadd, + const TargetInfo &genericadd_ho) { + genericadd.AddSource("src/main1.cpp"); + genericadd.Copy(genericadd_ho, { + SyncOption::IncludeDirs, + SyncOption::HeaderFiles, + }); + genericadd.Build(); + } diff --git a/docs/source/examples/mingw.rst b/docs/source/examples/mingw.rst new file mode 100644 index 00000000..f8fd86c8 --- /dev/null +++ b/docs/source/examples/mingw.rst @@ -0,0 +1,173 @@ +MinGW +======= + +Lowlevel MinGW Tests + + +Executable +----------- + +.. code-block:: cpp + :linenos: + :emphasize-lines: 2,6 + + // MINGW specialized Toolchain + Toolchain_mingw mingw; + + // MINGW specialized targets + // Creates `Simple.exe` + ExecutableTarget_mingw exetarget("Simple", mingw, ""); + exetarget.GlobSources("src"); + exetarget.AddIncludeDir("include", true); + exetarget.Build(); + + // Build + tf::Executor executor; + executor.run(exetarget.GetTaskflow()); + executor.wait_for_all(); + +StaticLib +---------- + +.. code-block:: cpp + :linenos: + :emphasize-lines: 2,6,13 + + // MINGW specialized Toolchain + Toolchain_mingw mingw; + + // MINGW specialized targets + // Creates `librandom.lib` + StaticTarget_mingw statictarget("librandom", mingw, ""); + statictarget.AddSource("src/random.cpp"); + statictarget.AddIncludeDir("include", true); + statictarget.Build(); + + // MINGW specialized targets + // Creates `Simple.exe` + ExecutableTarget_mingw exetarget("Simple", mingw, ""); + exetarget.AddSource("src/main.cpp"); + exetarget.AddIncludeDir("include", true); + exetarget.AddLibDep(statictarget); + exetarget.Build(); + + // Build + tf::Executor executor; + tf::Taskflow taskflow; + + // Setup your dependencies explicitly + // Here statictarget needs to be built before exetarget + auto statictargetTask = taskflow.composed_of(statictarget.GetTaskflow()); + auto exetargetTask = taskflow.composed_of(exetarget.GetTaskflow()); + exetargetTask.succeed(statictargetTask); + + executor.run(taskflow); + executor.wait_for_all(); + +DynamicLib +----------- + +.. code-block:: cpp + :linenos: + :emphasize-lines: 2,6,13 + + // MINGW specialized Toolchain + Toolchain_mingw mingw; + + // MINGW specialized targets + // Creates `librandom.lib` and `librandom.lib.dll` + DynamicTarget_mingw dynamictarget("librandom", mingw, ""); + dynamictarget.AddSource("src/random.cpp"); + dynamictarget.AddIncludeDir("include", true); + dynamictarget.Build(); + + // MINGW specialized target + // Creates `Simple.exe` which uses the above dynamic library + ExecutableTarget_mingw exetarget("Simple", mingw, ""); + exetarget.AddSource("src/main.cpp"); + exetarget.AddIncludeDir("include", true); + exetarget.AddLibDep(dynamictarget); + exetarget.Build(); + + // Build + tf::Executor executor; + tf::Taskflow taskflow; + + // Setup your dependencies explicitly + // Here dynamictarget needs to be built before exetarget + auto dynamictargetTask = taskflow.composed_of(dynamictarget.GetTaskflow()); + auto exetargetTask = taskflow.composed_of(exetarget.GetTaskflow()); + exetargetTask.succeed(dynamictargetTask); + + executor.run(taskflow); + executor.wait_for_all(); + + // Now that both your targets are built, copy the dynamictarget DLL to the exetarget location + // This is required for your exetarget to run properly + if (exetarget.IsBuilt()) { + fs::path copy_to_path = + exetarget.GetTargetBuildDir() / dynamictarget.GetTargetPath().filename(); + fs::remove_all(copy_to_path); + fs::copy(dynamictarget.GetTargetPath(), copy_to_path); + } + + +.. note:: Our ``ExecutableTarget_mingw`` depends on ``DynamicTarget_mingw`` and requires the ``librandom.dll`` file to be present in the same folder location as the executable when running. + +PrecompileHeader +------------------- + +.. code-block:: cpp + :linenos: + :emphasize-lines: 2,4,7,24,25,26,39,40,41 + + int main() { + Toolchain_mingw mingw; + + ExecutableTarget_mingw g_cppflags("cppflags", mingw, "files"); + cppflags_build_cb(g_cppflags); + + ExecutableTarget_mingw g_cflags("cflags", mingw, "files"); + cflags_build_cb(g_cflags); + + tf::Executor executor; + tf::Taskflow taskflow; + + taskflow.composed_of(g_cppflags.GetTaskflow()); + taskflow.composed_of(g_cflags.GetTaskflow()); + executor.run(taskflow); + executor.wait_for_all(); + } + + static void cppflags_build_cb(BaseTarget &cppflags) { + cppflags.AddSource("main.cpp", "src"); + cppflags.AddSource("random.cpp", "src"); + cppflags.AddIncludeDir("include", true); + + cppflags.AddPch("pch/pch_cpp.h"); + cppflags.AddPch("pch/pch_c.h"); + cppflags.AddIncludeDir("pch", true); + + cppflags.AddPreprocessorFlag("-DRANDOM=1"); + cppflags.AddCppCompileFlag("-Wall"); + cppflags.AddCppCompileFlag("-Werror"); + cppflags.AddLinkFlag("-lm"); + + cppflags.Build(); + } + + static void cflags_build_cb(BaseTarget &cflags) { + cflags.AddSource("main.c", "src"); + + cflags.AddPch("pch/pch_c.h"); + cflags.AddIncludeDir("pch", false); + cflags.AddHeader("pch/pch_c.h"); + + cflags.AddPreprocessorFlag("-DRANDOM=1"); + cflags.AddCCompileFlag("-Wall"); + cflags.AddCCompileFlag("-Werror"); + cflags.AddLinkFlag("-lm"); + + cflags.Build(); + } + diff --git a/docs/source/examples/msvc.rst b/docs/source/examples/msvc.rst new file mode 100644 index 00000000..1fe6a524 --- /dev/null +++ b/docs/source/examples/msvc.rst @@ -0,0 +1,118 @@ +MSVC +===== + +Lowlevel MSVC Tests + +Executable +----------- + +.. code-block:: cpp + :linenos: + :emphasize-lines: 2,6 + + // MSVC specialized Toolchain + Toolchain_msvc msvc; + + // MSVC specialized targets + // Creates `Simple.exe` + ExecutableTarget_msvc exetarget("Simple", msvc, ""); + exetarget.GlobSources("src"); + exetarget.AddIncludeDir("include", true); + exetarget.Build(); + + // Build + tf::Executor executor; + executor.run(exetarget.GetTaskflow()); + executor.wait_for_all(); + +StaticLib +---------- + +.. code-block:: cpp + :linenos: + :emphasize-lines: 2,6,13 + + // MSVC specialized Toolchain + Toolchain_msvc msvc; + + // MSVC specialized targets + // Creates `librandom.lib` + StaticTarget_msvc statictarget("librandom", msvc, ""); + statictarget.AddSource("src/random.cpp"); + statictarget.AddIncludeDir("include", true); + statictarget.Build(); + + // MSVC specialized targets + // Creates `Simple.exe` + ExecutableTarget_msvc exetarget("Simple", msvc, ""); + exetarget.AddSource("src/main.cpp"); + exetarget.AddIncludeDir("include", true); + exetarget.AddLibDep(statictarget); + exetarget.Build(); + + // Build + tf::Executor executor; + tf::Taskflow taskflow; + + // Setup your dependencies explicitly + // Here statictarget needs to be built before exetarget + auto statictargetTask = taskflow.composed_of(statictarget.GetTaskflow()); + auto exetargetTask = taskflow.composed_of(exetarget.GetTaskflow()); + exetargetTask.succeed(statictargetTask); + + executor.run(taskflow); + executor.wait_for_all(); + +DynamicLib +----------- + +.. code-block:: cpp + :linenos: + :emphasize-lines: 2,6,13 + + // MSVC specialized Toolchain + Toolchain_msvc msvc; + + // MSVC specialized targets + // Creates `librandom.lib` and `librandom.lib.dll` + DynamicTarget_msvc dynamictarget("librandom", msvc, ""); + dynamictarget.AddSource("src/random.cpp"); + dynamictarget.AddIncludeDir("include", true); + dynamictarget.Build(); + + // MSVC specialized target + // Creates `Simple.exe` which uses the above dynamic library + ExecutableTarget_msvc exetarget("Simple", msvc, ""); + exetarget.AddSource("src/main.cpp"); + exetarget.AddIncludeDir("include", true); + exetarget.AddLibDep(dynamictarget); + exetarget.Build(); + + // Build + tf::Executor executor; + tf::Taskflow taskflow; + + // Setup your dependencies explicitly + // Here dynamictarget needs to be built before exetarget + auto dynamictargetTask = taskflow.composed_of(dynamictarget.GetTaskflow()); + auto exetargetTask = taskflow.composed_of(exetarget.GetTaskflow()); + exetargetTask.succeed(dynamictargetTask); + + executor.run(taskflow); + executor.wait_for_all(); + + // Now that both your targets are built, copy the dynamictarget DLL to the exetarget location + // This is required for your exetarget to run properly + if (exetarget.IsBuilt()) { + fs::copy(dynamictarget.GetDllPath(), + exetarget.GetTargetPath().parent_path() / + dynamictarget.GetDllPath().filename()); + } + + +.. note:: Our ``ExecutableTarget_msvc`` depends on ``DynamicTarget_msvc`` and requires the ``librandom.lib.dll`` file to be present in the same folder location as the executable when running. + +PrecompileHeader +------------------- + +TODO diff --git a/docs/source/examples/toc.rst b/docs/source/examples/toc.rst new file mode 100644 index 00000000..a38e30a7 --- /dev/null +++ b/docs/source/examples/toc.rst @@ -0,0 +1,10 @@ +Examples +========= + +.. toctree:: + + hybrid + gcc + msvc + clang + mingw diff --git a/docs/source/getting_started/all_compile_options.rst b/docs/source/getting_started/all_compile_options.rst new file mode 100644 index 00000000..cfcad3d7 --- /dev/null +++ b/docs/source/getting_started/all_compile_options.rst @@ -0,0 +1,115 @@ +Compile Options for BuildExe +============================ + +We can pass in configuration parameters through the Command Line but writing them inside a ``.toml`` file and passing it through the ``--config`` flag is much easier. + +Command Line options +--------------------- + +.. code-block:: shell + + Options: + -h,--help Print this help message and exit + --help-all Expand individual options. + --config Read .toml files. + --mode ENUM:value in {script->1,immediate->0} OR {1,0} REQUIRED + Provide BuildExe run mode + [Option Group: Root] + Options: + --clean Clean artifacts + --loglevel ENUM:value in {warning->3,info->2,debug->1,critical->5,trace->0} OR {3,2,1,5,0} + LogLevel settings + --root_dir TEXT REQUIRED Project root directory (relative to current directory) + --build_dir TEXT REQUIRED Project build dir (relative to current directory) + [Option Group: Project Info] + Options: + --name TEXT REQUIRED Provide Target name + --type ENUM:value in {dynamicLibrary->2,staticLibrary->1,executable->0} OR {2,1,0} REQUIRED + Provide Target Type + --relative_to_root TEXT REQUIRED + Provide Target relative to root + [Option Group: Target Inputs] + Options: + --srcs TEXT ... Provide source files + --includes TEXT ... Provide include dirs + --lib_dirs TEXT ... Provide lib dirs + --external_libs TEXT ... Provide external libs + --preprocessor_flags TEXT ... + Provide Preprocessor flags + --common_compile_flags TEXT ... + Provide CommonCompile Flags + --asm_compile_flags TEXT ... + Provide AsmCompile Flags + --c_compile_flags TEXT ... Provide CCompile Flags + --cpp_compile_flags TEXT ... + Provide CppCompile Flags + --link_flags TEXT ... Provide Link Flags + + Subcommands: + toolchain + Select Toolchain + Supported Toolchains: + host Host Toolchain + + target + Select Target + + script + Options: + --configs TEXT ... Config files for script mode + +TOML file options +------------------- + +Relate the options below with the **Command Line options** above. + +You can also read the `CLI11 README `_ + +.. code-block:: toml + + # Default (ungrouped) Options + mode = "script" # REQUIRED script, immediate + + # Root Options + clean = true # true, false + loglevel = "trace" # "trace", "debug", "info", "warning", "critical" + root_dir = "" # REQUIRED + build_dir = "" # REQUIRED + + # Target Info Options + name = "" # REQUIRED + type = "executable" # REQUIRED, executable, staticLibrary, dynamicLibrary + relative_to_root = "" # REQUIRED + + # Target Inputs Options + srcs = [""] + includes = [""] + lib_dirs = [""] + external_libs = [""] + preprocessor_flags = [""] + common_compile_flags = [""] + asm_compile_flags = [""] + c_compile_flags = [""] + cpp_compile_flags = [""] + link_flags = [""] + + # Subcommand + + # Host Toolchain Options + [toolchain.host] # ALWAYS + build = true # ALWAYS + test = false # ALWAYS + + id = "gcc" + name = "x86_64-linux-gnu" + asm_compiler = "as" + c_compiler = "gcc" + cpp_compiler = "g++" + archiver = "ar" + linker = "ld" + + # TODO, Add more options to narrow down search when multiple toolchains are installed + + # Script Options + [script] + configs = ["build.toml", "custom_toolchain.toml"] # Converted to --config build.toml --config custom_toolchain.toml diff --git a/docs/source/getting_started/all_default_build_options.rst b/docs/source/getting_started/all_default_build_options.rst new file mode 100644 index 00000000..4d17c8e0 --- /dev/null +++ b/docs/source/getting_started/all_default_build_options.rst @@ -0,0 +1,78 @@ +Build Options for "scripts" +============================ + +We can pass in configuration parameters through the Command Line but writing them inside a ``.toml`` file and passing it through the ``--config`` flag is much easier. + +Command Line options +--------------------- + +.. code-block:: shell + + Options: + -h,--help Print this help message and exit + --help-all Expand individual options. + --config Read .toml files. + [Option Group: Root] + Options: + --clean Clean artifacts + --loglevel ENUM:value in {warning->3,info->2,debug->1,critical->5,trace->0} OR {3,2,1,5,0} + LogLevel settings + --root_dir TEXT REQUIRED Project root directory (relative to current directory) + --build_dir TEXT REQUIRED Project build dir (relative to current directory) + + Subcommands: + toolchain + Select Toolchain + Supported Toolchains: + gcc Generic gcc toolchain + + target + Select Target + +TOML file options +------------------- + +Relate the options below with the **Command Line options** above. + +You can also read the `CLI11 README `_ + +.. code-block:: toml + + # Root Options + clean = true # true, false + loglevel = "trace" # "trace", "debug", "info", "warning", "critical" + root_dir = "" # REQUIRED + build_dir = "" # REQUIRED + + # Subcommand + + # Host Toolchain Options + [toolchain.gcc] # DEPENDS on user + + # Run time way to select your build and test options during registration + # Valid options + # build = false, test = false, target not built or tested + # build = true, test = false, target built but not tested + # build = true, test = true, target built and tested (users responsiblity for a testable target) + build = true # REQUIRED + test = true # REQUIRED + + # Run time way to change the compiler on the fly + # Not recommended + # Prefer to use the specialized Toolchains during Compile time (See build.cpp in examples) + # id = "gcc" + # name = "x86_64-linux-gnu" + # asm_compiler = "as" + # c_compiler = "gcc" + # cpp_compiler = "g++" + # archiver = "ar" + # linker = "ld" + + # TODO, Add more options to narrow down search when multiple toolchains are installed + + +.. note:: These are the default build options during "script" mode. + + Users can also add custom arguments using CLI11 using the ``.Ref()`` API and using them in the ``.toml`` file. + + Please make sure to read the `CLI11 README `_ for those APIs. diff --git a/docs/source/getting_started/buildexe_immediate_example.rst b/docs/source/getting_started/buildexe_immediate_example.rst new file mode 100644 index 00000000..9b0e882b --- /dev/null +++ b/docs/source/getting_started/buildexe_immediate_example.rst @@ -0,0 +1,94 @@ +BuildExe "Immediate" example +============================= + +Basic Procedure +---------------- + +BuildExe has an "immediate" mode where it can directly provide us the Target (singular) without going through the intermediate steps that we followed in the "script" mode. + +.. uml:: + + usecase "main.cpp" as main_cpp + usecase "compile.toml" as compile_toml + usecase "host_or_cross_toolchain.toml" as host_or_cross_toolchain + + rectangle "./buildexe" as buildexe_exe + artifact "./hello_world" as hello_world_exe + + main_cpp -right-> buildexe_exe + compile_toml -up-> buildexe_exe + host_or_cross_toolchain -up-> buildexe_exe + buildexe_exe -right-> hello_world_exe + + +What is the point of the "script" mode then? +++++++++++++++++++++++++++++++++++++++++++++ + +The "immediate" mode has a lot of limitations but it is also useful in certain scenarios + +**Limitations** + +* Cannot build more than one target at a time +* No customizability allowed. + * Which means that apart from just building the target you cannot do anything else. + * For example: Setting dependencies between targets, running custom generators, running static analysis tools and so on. + +**Usecase** + +* Simple way to build one target. +* Completely run time dependent. Change your ``build.toml`` file and you can build a new target. +* Very easy to know how a particular target is built. + * For example. In a large project it might be very hard to visualize how a single target is built due to various code generation and library / target dependencies. + * Since .toml is easily readable, we can understand the sources, search directories and flags that the target requires at a glance. +* Can be shipped to customers for a pain free build process i.e removes technical debt. + * Building your artifact is as simple as ``buildexe --config build.toml --config %BUILDCC_HOME/host/host_or_cross_toolchain.toml`` + * build.toml contains the target information. + * host_or_cross_toolchain.toml contains the host/cross toolchain information + * We can combine the two into one .toml file. + + +Helloworld "immediate" example +------------------------------ + +* Write your ``build.toml`` file +* Invoke ``buildexe`` from the command line from the **[workspace]** folder + * Pay attention to the ``root_dir`` and ``build_dir`` parameters set in your ``build.toml`` file. + * These directories are relative to the directory from which you **invoke** buildexe + +.. code-block:: bash + + ./buildexe --config build.toml --config $BUILDCC_HOME/host/host_toolchain.toml + +* Your target will now be present in ``[build_dir]/[toolchain_name]/[target_name]`` (taken from ``build.toml``) + +Directory structure +++++++++++++++++++++ + +.. uml:: + + @startmindmap + * [workspace] + ** [src] + *** main.cpp + ** build.toml + @endmindmap + +Write your ``build.toml`` file ++++++++++++++++++++++++++++++++ + +.. code-block:: toml + + # Settings + root_dir = "" + build_dir = "_build" + loglevel = "info" + clean = false + + # BuildExe run mode + mode = "immediate" + + # Target information + name = "hello_world" + type = "executable" + relative_to_root = "src" + srcs = ["main.cpp"] diff --git a/docs/source/getting_started/buildexe_package_manager.rst b/docs/source/getting_started/buildexe_package_manager.rst new file mode 100644 index 00000000..5e9493c8 --- /dev/null +++ b/docs/source/getting_started/buildexe_package_manager.rst @@ -0,0 +1,347 @@ +BuildExe as a Package Manager +============================= + +When we are **compiling** our "script" to an executable we can also add additional library build files which define how the library is built. + +The procedure is similar to git cloning the library to the **ENV{BUILDCC_HOME}/libs** folder using the ``libs`` options in buildexe. + +Please see :doc:`buildexe_setup` to setup your libs folder appropriately. + +Basic Procedure +---------------- + +.. uml:: + + usecase "build.user_project.cpp" as build_cpp + usecase "libs/library/build.library.h" as build_lib_header + usecase "libs/library/build.library.cpp" as build_lib_source + + usecase "compile.toml" as compile_toml + usecase "host_toolchain.toml" as host_toolchain_toml + rectangle "./build.user_project" as build_project_exe + usecase "build.toml" as build_toml + + rectangle "./buildexe" as buildexe_exe + + artifact "library" as library + artifact "./hello_world" as hello_world_exe + + build_cpp -right-> buildexe_exe + build_lib_header -right-> buildexe_exe + build_lib_source -right-> buildexe_exe + + compile_toml -up-> buildexe_exe + host_toolchain_toml -up-> buildexe_exe + + buildexe_exe -right-> build_project_exe + + build_toml -up-> build_project_exe + build_project_exe -right-> hello_world_exe + + library -up-> hello_world_exe + +Helloworld "fmtlib" example +---------------------------- + +* Git clone the ``fmt`` library into your ``ENV{BUILDCC_HOME}/libs`` folder +* Run ``buildexe libs --help-all``. + * You should automatically see the library folder name pop up under the ``libs`` submodule. + * In this case it will be the ``fmt`` option. + +.. code-block:: shell + :linenos: + :emphasize-lines: 8 + + script + Options: + --configs TEXT ... Config files for script mode + + libs + Libraries + Options: + --fmt TEXT ... fmt library + +* Since we want to use the ``fmt`` library in our project we can now write our ``compile.toml`` file as given below. (See highlighted lines) +* We then write our "script", include the ``fmt`` build header file and define our targets and dependencies. +* Lastly we invoke buildexe to build our project + +.. code-block:: bash + + buildexe --config compile.toml --config $BUILDCC_HOME/host/host_toolchain.toml + +Directory structure ++++++++++++++++++++++ + +.. uml:: + + @startmindmap + * [workspace] + ** [src] + *** main.cpp + ** build.helloworld.cpp + ** compile.toml + ** build.toml + @endmindmap + +Write your fmtlib build files +++++++++++++++++++++++++++++++ + +.. note:: This process might seem like a hassle. But please note that fmtlib does not currently have support for BuildCC like build files and it must be provided by the user. + +.. code-block:: cpp + :linenos: + :caption: build.fmt.h + + #pragma once + + #include "buildcc.h" + + using namespace buildcc; + + /** + * @brief User configurable options + * default_flags: Adds default preprocessor, compile and link flags to the fmt + * library if true. If false these would need to be provided by the user. + */ + struct FmtConfig { + bool default_flags{true}; + // NOTE, Add more options here as required to customize your fmtlib build + }; + + /** + * @brief Build the libfmt static or dynamic library + * + * @param target Initialized specialized library target + * @param config See FmtConfig above + */ + void build_fmt_cb(BaseTarget& target, const FmtConfig& config = FmtConfig()); + + /** + * @brief Information for fmt header only library + * + * @param target_info Holds the include dirs, headers and preprocessor flag + * information + */ + void build_fmt_ho_cb(TargetInfo& target_info); + +.. code-block:: cpp + :linenos: + :caption: build.fmt.cpp + + #include "build.fmt.h" + + void build_fmt_cb(BaseTarget& target, const FmtConfig& config) { + target.AddSource("src/os.cc"); + target.AddSource("src/format.cc"); + target.AddIncludeDir("include", false); + target.GlobHeaders("include/fmt"); + + // Toolchain specific flags added + // if default_flags == true + if (config.default_flags) { + switch (target.GetToolchain().GetId()) { + case ToolchainId::Gcc: + target.AddCppCompileFlag("-std=c++11"); + break; + case ToolchainId::MinGW: + target.AddCppCompileFlag("-std=c++11"); + break; + case ToolchainId::Msvc: + target.AddCppCompileFlag("/std:c++11"); + break; + default: + break; + } + } + + // Register your fmt lib tasks + target.Build(); + } + + void build_fmt_ho_cb(TargetInfo& target_info) { + target_info.AddIncludeDir("include", false); + target_info.GlobHeaders("include/fmt"); + target_info.AddPreprocessorFlag("-DFMT_HEADER_ONLY=1"); + } + + +Write your C++ "script" +++++++++++++++++++++++++ + +* Boilerplate is similar to the BuildExe helloworld "script" example in :doc:`buildexe_script_example` + +**Core build setup is highlighted below** + +* On line 4 we include our ``build.fmt.h`` include file. See ``compile.toml`` libs submodule to correlate +* On line 8 we include the ``buildexe_lib_dirs.h`` include file. This is a generated include file which contains the absolute paths of the library folders. + * Access is through ``BuildExeLibDir::[lib_folder_name]`` + * This is the reason why we need to make sure that our git cloned library folder name is also a valid C++ variable name. +* On line 40 we point to the absolute ``fmt`` libs folder path for the sources and **redirect** the output to our ``env::get_project_build_dir() / "fmt"`` folder. + * In this way we can safely use out of root projects and redirect the output files to our build location + * There are other input source -> output object redirect options through additional APIs. +* On line 43 and 44 we directly use our fmtlib build APIs to define how fmtlib should be built +* On line 47 and 48 we define our Hello World executable target + * See ``main.cpp`` below for fmtlib hello world example + * See ``hello_world_build_cb`` for build steps +* On line 79 ``hello_world_build_cb`` in additional to compiling our ``main.cpp`` file + * We need to link our compiled ``fmt_lib`` using the ``AddLibDep`` API + * We also insert the ``fmt_lib`` include dirs to the hello world target since we need to ``#include "fmt/format.h"`` in ``main.cpp`` +* On line 52 we register a dependency of ``fmt_lib`` on ``hello_world``. + * This guarantees that the fmt library will be built before the hello world executable. + * This is essential because we need to **link** fmtlib with our hello world executable. + +.. code-block:: cpp + :linenos: + + #include "buildcc.h" + + // Included through libs + #include "build.fmt.h" + + // Generated by BuildCC + // See the `_build_internal` directory + #include "buildexe_lib_dirs.h" + + using namespace buildcc; + + // Function Prototypes + static void clean_cb(); + static void hello_world_build_cb(BaseTarget &target, BaseTarget &fmt_lib); + + int main(int argc, char **argv) { + // Get arguments + ArgToolchain arg_gcc; + + Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc) + .Parse(argc, argv); + + // Initialize your environment + Reg::Init(); + + // Pre-build steps + Reg::Call(Args::Clean().Func(clean_cb); + + // Build steps + // Explicit toolchain - target pairs + Toolchain_gcc gcc; + + // Setup your [Library]Target_[toolchain] fmtlib instance + // Update your TargetEnv to point to `BuildExeLibDir::fmt` folder + // The generated build files will go into your current `project_build_dir / fmt` folder + StaticTarget_gcc fmt_lib( + "libfmt", gcc, + TargetEnv(BuildExeLibDir::fmt, env::get_project_build_dir() / "fmt")); + + // We use the build.fmt.h and build.fmt.cpp APIs to define how we build our fmtlib + FmtConfig fmt_config; + + // Define our hello world executable + ExecutableTarget_gcc hello_world("hello_world", gcc, ""); + + // Fmt lib is a dependency to the Hello world executable + // This means that fmt lib is guaranteed to be built before the hello world executable + Reg::Toolchain(arg_gcc.state) + .Build(arg_gcc.state, build_fmt_cb, fmt_lib, fmt_config) + .Build(hello_world_build_cb, hello_world, fmt_lib) + .Dep(hello_world, fmt_lib) + .Test("{executable}", hello_world);; + + + // Build and Test Target + // Builds libfmt.a and ./hello_world + Reg::Run(); + + // Post Build steps + // - Clang Compile Commands + plugin::ClangCompileCommands({&hello_world}).Generate(); + // - Graphviz dump + std::cout << reg.GetTaskflow().dump() << std::endl; + + return 0; + } + + static void clean_cb() { + fs::remove_all(env::get_project_build_dir()); + } + + static void hello_world_build_cb(BaseTarget &target, BaseTarget &fmt_lib) { + target.AddSource("main.cpp", "src"); + + // Add fmt_lib as a library dependency + target.AddLibDep(fmt_lib); + // We need to insert the fmt lib include dirs and header files into our hello_world executable target (naturally) + target.Insert(fmt_lib, { + SyncOption::IncludeDirs, + SyncOption::HeaderFiles, + }); + + // Register your tasks + target.Build(); + } + + +Write your ``compile.toml`` file +++++++++++++++++++++++++++++++++ + +* The only difference from the ``compile.toml`` in :doc:`buildexe_script_example` is the additional of the ``libs`` submodule +* We use the ``fmt`` option since we git cloned the library into the libs folder +* We add the various fmt build files that need to be compiled with our "script" +* See highlighed lines 19 and 20 + +.. code-block:: toml + :linenos: + :emphasize-lines: 19,20 + + # Settings + root_dir = "" + build_dir = "_build_internal" + loglevel = "debug" + clean = false + + # BuildExe run mode + mode = "script" + + # Target information + name = "single" + type = "executable" + relative_to_root = "" + srcs = ["build.main.cpp"] + + [script] + configs = ["build.toml"] + + [libs] + fmt = ["build.fmt.cpp", "build.fmt.h"] + + +Write your ``build.toml`` file ++++++++++++++++++++++++++++++++ + +* Exact same ``build.toml`` as seen in the document :doc:`buildexe_script_example` + +.. code-block:: toml + :linenos: + + # Root + root_dir = "" + build_dir = "_build" + loglevel = "debug" + clean = true + + # Toolchain + [toolchain.gcc] + build = true + test = true + + +Write your ``main.cpp`` helloworld example in fmtlib +++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. code-block:: cpp + + #include "fmt/format.h" + int main() { + fmt::print("{} {}", "Hello", "World"); + return 0; + } diff --git a/docs/source/getting_started/buildexe_script_example.rst b/docs/source/getting_started/buildexe_script_example.rst new file mode 100644 index 00000000..ca229500 --- /dev/null +++ b/docs/source/getting_started/buildexe_script_example.rst @@ -0,0 +1,213 @@ +BuildExe "Script" example +============================= + +Basic Procedure +---------------- + +Since we are writing our scripts in C++ we first need to **compile** our "script" to an executable, and then **execute** it to build targets. + +.. uml:: + + usecase "build.helloworld.cpp" as build_cpp + usecase "compile.toml" as compile_toml + usecase "host_toolchain.toml" as host_toolchain_toml + usecase "./build.helloworld" as build_project_exe + usecase "build.toml" as build_toml + + rectangle "./buildexe" as buildexe_exe + artifact "./hello_world" as hello_world_exe + + build_cpp -right-> buildexe_exe + compile_toml -up-> buildexe_exe + host_toolchain_toml -up-> buildexe_exe + buildexe_exe -right-> build_project_exe + build_toml -up-> build_project_exe + build_project_exe -right-> hello_world_exe + + +.. attention:: Limitation of **script** mode + + We need to compile our build "script" using a **HOST** toolchain. + We cannot use a cross compiler here. + +Helloworld "script" example +--------------------------- + +* Write your C++ "script" +* Write your ``compile.toml`` file +* Write your ``build.toml`` file +* Invoke ``buildexe`` from the command line from the **[workspace]** folder + * Pay attention to the ``root_dir`` and ``build_dir`` parameters set in your ``compile.toml`` and ``build.toml`` file. + * These directories are relative to the directory from which you **invoke** buildexe + +.. code-block:: bash + + ./buildexe --config compile.toml --config $BUILDCC_HOME/host/host_toolchain.toml + +* Your target will now be present in ``[build_dir]/[toolchain_name]/[target_name]`` (taken from ``build.toml`` and ``build.helloworld.cpp``) + +Directory structure +++++++++++++++++++++ + +.. uml:: + + @startmindmap + * [workspace] + ** [src] + *** main.cpp + ** build.helloworld.cpp + ** compile.toml + ** build.toml + @endmindmap + +Write your C++ "script" +++++++++++++++++++++++++ + +From the "script" below we can see that we have a few lines of **boilerplate** + +* Setup args +* Setup register (pre and post callback requirements) + +We then setup our main **toolchain**-**target** pairs. Highlighted below + +* Specify your toolchain + * Verify the toolchain existance on your machine by using the ``.Verify`` API + * If multiple similar toolchains are detected (due to multiple installations), the first found toolchain is picked + * You can pass in the ``VerifyToolchainConfig`` to narrow down your search and verification. +* Specify your compatible target + * Every specific target is meant to use a specific target. + * For example: ``ExecutableTarget_gcc`` specialized target can use the ``Toolchain_gcc`` specialized toolchain but not ``Toolchain_msvc``. +* Use the Register ``.Build`` API. We use callbacks here to avoid cluttering our ``int main`` function. + * ``arg_gcc.state`` contains our ``build`` and ``test`` values passed in from ``build.toml`` (see below). The ``.Build`` API conditionally selects the target at run time. + * **IMPORTANT** Please do not forget to invoke the Target ``.Build`` API. This API registers the various ``CompileCommandTasks`` and ``LinkCommandTasks``. + * **IMPORTANT** In line with the above statement, Once the Target ``.Build`` API has been executed (tasks have been registered), do not attempt to add more information to the Targets. Internally the ``.Build`` API locks the target from accepting further input and any attempt to do so will ``std::terminate`` your program (this is by design). + +.. code-block:: cpp + :linenos: + :caption: build.helloworld.cpp + + #include "buildcc.h" + + using namespace buildcc; + + void clean_cb(); + // All specialized targets derive from BaseTarget + void hello_world_build_cb(BaseTarget & target); + + int main(int argc, char ** argv) { + // Setup your args + ArgToolchain arg_gcc; + + Args::Init() + .AddToolchain("gcc", "GCC toolchain", arg_gcc) + .Parse(argc, argv); + + // Register + Reg::Init(); + + // Pre build steps + // for example. clean your environment + Reg::Call(Args::Clean()).Func(clean_cb); + + // Build steps + // Main setup + Toolchain_gcc gcc; + ExecutableTarget_gcc hello_world("hello_world", gcc, ""); + + Reg::Toolchain(arg_gcc.state) + .Func([&](){ gcc.Verify() }) + .Build(hello_world_build_cb, hello_world); + + // Build your targets + Reg::Run(); + + // Post build steps + // for example. clang compile commands database + plugin::ClangCompileCommands({&hello_world}).Generate(); + + return 0; + } + + void clean_cb() { + fs::remove_all(env::get_project_build_dir()); + } + + void hello_world_build_cb(BaseTarget & target) { + // Add your source + target.AddSource("src/main.cpp"); + + // Initializes the target build tasks + target.Build(); + } + +Write your ``compile.toml`` file +++++++++++++++++++++++++++++++++ + +.. code-block:: toml + :linenos: + :caption: compile.toml + + # Settings + root_dir = "" + build_dir = "_build_internal" + loglevel = "info" + clean = false + + # BuildExe run mode + mode = "script" + + # Target information + name = "build.helloworld" + type = "executable" + relative_to_root = "" + srcs = ["build.helloworld.cpp"] + + [script] + configs = ["build.toml"] + +* ``root_dir`` tells BuildExe your project root directory relative from where it is invoked and ``build_dir`` tells BuildExe that the built artifacts should be inserted in this directory relative from where it is invoked. +* ``clean`` deletes your ``build_dir`` completely for a fresh setup. +* ``mode`` consists of **script** and **immediate** mode. See the **Basic Procedure** uml diagrams for a better understanding of the differences and purpose. + * Script Mode: :doc:`buildexe_script_example` + * Immediate Mode: :doc:`buildexe_immediate_example` +* Setup your target information + * ``name`` of your compiled "script" executable + * ``type`` MUST always be **executable** in script mode + * ``relative_to_root`` is a QOL feature to point to a path inside your root where the build "scripts" reside. + * ``srcs`` and equivalent are files that you want to compile. Please see :doc:`all_compile_options` for a full list of target options and inputs for script mode +* [script] submodule + * ``configs`` are .toml files passed to our compiled "script" executable. Please see :doc:`all_default_build_options` for a full list of default build options. + * The values inside ``configs`` are converted to ``--config [file].toml --config [file2].toml`` and so on and passed with the generated executable. + * In this example: ``./build.helloworld --config build.toml`` is run which generates your targets. + +Write your ``build.toml`` file ++++++++++++++++++++++++++++++++ + +.. code-block:: toml + :linenos: + :caption: build.toml + + # Root + root_dir = "" + build_dir = "_build" + loglevel = "debug" + + # Project + clean = false + + # Toolchain + [toolchain.gcc] + build = true + test = false + +* Please see the ``.cpp`` example above and correlate with these options. +* ``root_dir`` tells BuildExe your project root directory relative from where it is invoked and ``build_dir`` tells BuildExe that the built artifacts should be inserted in this directory relative from where it is invoked. +* ``clean`` invokes your ``clean_cb`` which determines how your build must be cleaned. In this example we delete the ``build_dir`` for a fresh setup. + +* [toolchain.gcc] submodule + * This is a nested submodule of ``toolchain`` -> ``gcc`` -> ``--build``, ``--test`` options and so on. + * The naming convention follows ``toolchain.[name]`` provided when using the ``.AddToolchain`` API. + * In our example: ``args.AddToolchain("gcc", "GCC toolchain", arg_gcc);`` + * The ``build`` and ``test`` values are used by the ``Register`` module. + * In our example ``arg_gcc.state.build`` and ``arg_gcc.state.test`` + * **REASONING** The reason why this has been done is because Buildcc allows your to mix multiple toolchains in a single script. We can now conditionally (at run time) choose the toolchains with which we would want to compile our targets. diff --git a/docs/source/getting_started/buildexe_setup.rst b/docs/source/getting_started/buildexe_setup.rst new file mode 100644 index 00000000..7c554971 --- /dev/null +++ b/docs/source/getting_started/buildexe_setup.rst @@ -0,0 +1,158 @@ +BuildExe Setup +============== + +ENV[BUILDCC_HOME] +----------------- + +* Add the environment variable ``BUILDCC_HOME`` with the absolute path on the operating system. For example: ``BUILDCC_HOME=C:\buildcc`` or ``BUILDCC_HOME=/local/mnt/buildcc`` +* Create directories **bin**, **extensions**, **libs** and **host** inside your ENV[BUILDCC_HOME] directory + * Download **BuildExe_Win.zip** or **BuildExe_Linux.zip** and unzip the bin file contents into the **bin** folder + * **extensions** and **libs** folder will be empty for the time being +* Update your ``PATH`` variable with the **bin** folder. + * For example: ``PATH += ENV[BUILDCC_HOME]/bin;ENV[PATH]`` + * Linux : ``export PATH="$BUILDCC_HOME/bin:$PATH"`` + * Windows : My Computer -> Properties -> Advanced System Settings -> Environment Variables -> [Update your system variables] + +* Git clone the buildcc project in the ENV[BUILDCC_HOME] directory. + +.. code-block:: bash + + git clone https://github.com/coder137/build_in_cpp.git buildcc + git submodule init + git submodule update + +* To update just do the following + +.. code-block:: bash + + git pull + git submodule init + git submodule update + +* Your ENV[BUILDCC_HOME] directory should look like the following + +.. uml:: + + @startmindmap + * ENV[BUILDCC_HOME] + ** bin + *** flatc + *** buildexe + ** buildcc + *** [git cloned] + ** extensions + *** [empty] + ** libs + *** [empty] + ** host + *** [empty] + @endmindmap + +Host Toolchains +------------------ + +From the above map we can see that the **host** folder is empty + +This folder will contain the .toml files of all the HOST toolchains present on your system. + +.. code-block:: toml + :linenos: + + [toolchain.host] + # Toolchain family id + # valid options are: gcc, msvc, mingw + # clang is not yet supported + id = "gcc" + + # NOTE: Each name MUST be unique + # Preferrably use the [id]_[target_arch]_[compiler_version] for uniqueness, + # but make sure the name is unique if you have multiple similar host toolchains installed on your system + name = "gcc_x86_64-linux-gnu_9.3.0" + + # Make sure all of these executables are present when you install your toolchain + asm_compiler = "as" + c_compiler = "gcc" + cpp_compiler = "g++" + archiver = "ar" + linker = "ld" + + # Additional information + # Necessary if multiple similar toolchains are installed + # For example. Installed gcc version 9.3.0 and 10.2.0 + path = "/usr/bin" + compiler_version = "9.3.0" + target_arch = "x86_64-linux-gnu" + +.. important:: For **Windows**, make sure to use ``vcvarsall.bat [flavour]`` to activate your toolchain + +* Your ENV[BUILDCC_HOME] directory should look like the following + +.. uml:: + + @startmindmap + * ENV[BUILDCC_HOME] + ** bin + *** flatc + *** buildexe + ** buildcc + *** [git cloned] + ** extensions + *** [empty] + ** libs + *** [empty] + ** host + *** gcc_x86_64-linux-gnu_9.3.0.toml + *** mingw_x86_64-w64-mingw32_10.2.0.toml + *** msvc_am64_19.29.30137.toml + @endmindmap + +Libs +------- + +* Any library that the user needs to use in their projects can be **git cloned** into the libs folder. +* BuildExe can then be used to compile the library build files along with the "script". +* The library build files define how a particular library is built. +* These functions can be used in the "script" to define how your target should be built. + +In this way we achieve a package manager like functionality with BuildExe and git. (Local package manager) + +For example we download the **fmt library** to our libs folder + +.. code-block:: bash + + cd $BUILDCC_HOME/libs + git clone https://github.com/fmtlib/fmt.git + +.. important:: This might sound strange, but the git cloned library **folder name** must also be a valid C++ **variable name**. + +* Since the **fmt** lib does not have support for BuildCC style build files we write our own +* Please see the :doc:`buildexe_package_manager` document for a simple example. + +.. uml:: + + @startmindmap + * ENV[BUILDCC_HOME] + ** bin + *** flatc + *** buildexe + ** buildcc + *** [git cloned] + ** extensions + *** [empty] + ** libs + *** fmt + **** [git cloned] + *** spdlog + **** [git cloned] + *** json + **** [git cloned] + ** host + *** gcc_x86_64-linux-gnu_9.3.0.toml + *** mingw_x86_64-w64-mingw32_10.2.0.toml + *** msvc_am64_19.29.30137.toml + @endmindmap + +Extensions +------------- + +.. note:: Extensions support in BuildExe is incomplete and there currently aren't any third party extensions for BuildCC. diff --git a/docs/source/getting_started/toc.rst b/docs/source/getting_started/toc.rst new file mode 100644 index 00000000..b60c4826 --- /dev/null +++ b/docs/source/getting_started/toc.rst @@ -0,0 +1,12 @@ +Getting Started +================= + +.. toctree:: + + buildexe_setup + buildexe_script_example + buildexe_immediate_example + buildexe_package_manager + walkthroughs + all_compile_options + all_default_build_options diff --git a/docs/source/getting_started/walkthroughs.rst b/docs/source/getting_started/walkthroughs.rst new file mode 100644 index 00000000..5fcc4dce --- /dev/null +++ b/docs/source/getting_started/walkthroughs.rst @@ -0,0 +1,44 @@ +Walkthrough Examples +===================== + +.. note:: Provide a gist of the various APIs and features present in BuildCC + +* Simple +* IncludeDirs +* LibDeps +* Flags (Preprocessor, Compile and Link flags) +* Generators +* Target Info + +TODO +------ + +* Custom Targets +* Custom Arguments +* Debugging + +Simple +------- + +* See ``SourceApi`` + +IncludeDirs +------------ + +* See ``IncludeDirApi`` +* Read why Header files and Include dirs are tracked + +LibDeps +--------- + +* See ``LibApi`` + +Flags +------- + +* See ``FlagApi`` + +Generator +--------- + +* See :doc:`../user_api/generator` diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 00000000..96a617c5 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,25 @@ +.. BuildCC documentation master file, created by + sphinx-quickstart on Sun Dec 19 02:26:23 2021. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to BuildCC's documentation! +=================================== + +.. toctree:: + :maxdepth: 1 + :caption: Contents + + intro/toc + arch/toc + getting_started/toc + user_api/toc + examples/toc + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/intro/toc.rst b/docs/source/intro/toc.rst new file mode 100644 index 00000000..46b05085 --- /dev/null +++ b/docs/source/intro/toc.rst @@ -0,0 +1,73 @@ +Introduction +============= + +Aim +---- + +BuildCC aims to be an alternative to Makefiles while using the feature rich C++ language instead of a custom DSL. + +Features +---------- + +* Complete flexibility for custom workflows and toolchains +* C++ language feature benefits and debuggable build binaries +* Optimized rebuilds through serialization. + * Can optimize for rebuilds by comparing the previous stored build with current build. + * Further details given in **Architecture** +* Customizable for community plugins. + +Pre-requisites +-------------- + +* Technical Knowledge + * C++03 classes and template usage +* C++17 Compiler with + * C++17 filesystem library support + * C++11 thread library support +* Third Party Libraries (See License below) + * JSON v3.11.2 + * Taskflow v3.1.0 + * CLI11 v2.1.0 + * Tiny Process Library v2.0.4 + * fmt v8.0.1 + * spdlog v1.9.2 + * CppUTest v4.0 + +General Information +------------------- + +* A one stage **input / output** procedure is called a **Generator** with a wide variety of use cases + * Single input creates single output + * Single input creates multiple outputs + * Multiple inputs create single output + * Multiple inputs creates multiple outputs +* A two stage **compile** and **link** procedure is called a **Target** + * This means that Executables, StaticLibraries and DynamicLibraries are all categorized as Targets + * In the future C++20 modules can also be its own target depending on compiler implementations +* Every Target requires a complementary (and compatible) **Toolchain** + * This ensures that cross compiling is very easy and explicit in nature. + * Multiple toolchains can be `mixed` in a single build file i.e we can generate targets using the GCC, Clang, MSVC and many other compilers **simultaneously**. +* The **compile_command** (pch and object commands) and **link_command** (target command) is fed to the **process** call to invoke the **Toolchain**. +* Each **Target** can depend on other targets efficiently through Parallel Programming using **Taskflow**. + * Dependency between targets is explicitly mentioned through the Taskflow APIs + * This has been made easier for the user through the ``buildcc::Register`` module. +* Build files can be customized through command line arguments + * Command line arguments can be stored in configurable ``.toml`` files and passed using the ``--config`` flag. + * Users can define their own custom arguments. + * Argument passing has been made easy using the ``buildcc::Args`` module. + +Licenses +--------- + +`BuildCC` is licensed under the Apache License, Version 2.0. See **LICENSE** for the full license text. `BuildCC` aims to use open-source libraries containing permissive licenses. + +.. note:: Developers who would like to suggest an alternative library, raise an issue with the **license** and **advantages** clearly outlined. + +* `Fmtlib `_ (Formatting) [MIT License] [Header Only] +* `Spdlog `_ (Logging) [MIT License] [Header Only] +* `Tiny Process Library `_ (Process handling) [MIT License] +* `Taskflow `_ (Parallel Programming) [MIT License] [Header Only] + * See also `3rd-Party `_ used by Taskflow +* `Nlohmann::Json `_ (Serialization) [MIT License] [Header Only] +* `CLI11 `_ (Argument Parsing) [BSD-3-Clause License] [Header Only] +* `CppUTest `_ (Unit Testing/Mocking) [BSD-3-Clause License] diff --git a/docs/source/user_api/args.rst b/docs/source/user_api/args.rst new file mode 100644 index 00000000..5c7bbd72 --- /dev/null +++ b/docs/source/user_api/args.rst @@ -0,0 +1,83 @@ +Args +===== + +args.h +------- + +.. doxygenclass:: buildcc::Args + +.. doxygenstruct:: buildcc::ArgCustom + +.. doxygenstruct:: buildcc::ArgToolchainState + +.. doxygenclass:: buildcc::ArgToolchain + +.. doxygenstruct:: buildcc::ArgTarget + +Example +--------- + +.. code-block:: cpp + :linenos: + + using namespace buildcc; + + struct CustomData : ArgCustom { + void Add(CLI::App & app) override { + // setup your app from data1, data2, data3, data... + // NOTE: The Add method should not be invoked by the user + // NOTE: The Add method is only expected to be invoked once, not multiple times. + } + + std::string data1; + int data2; + float data3; + // etc + }; + + int main(int argc, char ** argv) { + ArgToolchain arg_gcc_toolchain; + ArgCustomData custom_data; + Args::Init() + .AddToolchain("gcc", "Generic GCC toolchain", arg_gcc_toolchain) + .AddCustomCallback([](CLI::App &app) {}); + .AddCustomData(custom_data); + .Parse(argc, argv); + + // Root + Args::GetProjectRootDir(); // Contains ``root_dir`` value + Args::GetProjectBuildDir(); // Contains ``build_dir`` value + Args::GetLogLevel(); // Contains ``loglevel`` enum + Args::Clean(); // Contains ``clean`` value + + // Toolchain + // .build, .test + arg_gcc_toolchain.state; + // .id, .name, .asm_compiler, .c_compiler, .cpp_compiler, .archiver, .linker -> BaseToolchain + auto &gcc_toolchain = arg_gcc_toolchain; + gcc_toolchain.SetToolchainInfoFunc(GlobalToolchainInfo::Get(gcc_toolchain.id)); + return 0; + } + +.. code-block:: toml + :linenos: + + # Root + root_dir = "" + build_dir = "_build" + loglevel = "trace" + clean = true + + # Toolchain + [toolchain.gcc] + build = true + test = true + + id = "gcc" + name = "x86_64-linux-gnu" + asm_compiler = "as" + c_compiler = "gcc" + cpp_compiler = "g++" + archiver = "ar" + linker = "ld" + diff --git a/docs/source/user_api/environment.rst b/docs/source/user_api/environment.rst new file mode 100644 index 00000000..07a869af --- /dev/null +++ b/docs/source/user_api/environment.rst @@ -0,0 +1,86 @@ +Environment +=========== + + +env.h +----- + +.. doxygenclass:: buildcc::Project + +logging.h +--------- + +.. doxygenfunction:: log_trace + +.. doxygenfunction:: log_debug + +.. doxygenfunction:: log_info + +.. doxygenfunction:: log_warning + +.. doxygenfunction:: log_critical + +assert_fatal.h +-------------- + +.. doxygenfunction:: assert_fatal([[maybe_unused]] const char *) + +.. doxygenfunction:: assert_fatal(const std::string &) + +.. doxygenfunction:: assert_fatal(bool, const char *) + +.. doxygenfunction:: assert_fatal(bool, const std::string &) + +.. doxygendefine:: ASSERT_FATAL + +command.h +--------- + +.. doxygenclass:: buildcc::env::Command + +host_compiler.h +---------------- + +.. doxygenfunction:: is_gcc + +.. doxygenfunction:: is_mingw + +.. doxygenfunction:: is_clang + +.. doxygenfunction:: is_msvc + +host_os.h +---------- + +.. doxygenfunction:: is_linux + +.. doxygenfunction:: is_win + +.. doxygenfunction:: is_mac + +.. doxygenfunction:: is_unix + +.. doxygenenum:: OsId + +.. doxygenfunction:: get_host_os + +host_os_util.h +---------------- + +.. doxygenfunction:: get_os_envvar_delim + +.. doxygenfunction:: get_os_executable_extension + +task_state.h +-------------- + +.. doxygenenum:: TaskState + +.. doxygenfunction:: get_task_state + +util.h +--------- + +.. doxygenfunction:: save_file(const char *, const std::string &, bool) + +.. doxygenfunction:: load_file diff --git a/docs/source/user_api/generator.rst b/docs/source/user_api/generator.rst new file mode 100644 index 00000000..364a1cf0 --- /dev/null +++ b/docs/source/user_api/generator.rst @@ -0,0 +1,54 @@ +Template Generator +=================== + +> TODO, + +File Generator +=============== + +> TODO + +Custom Generator +================ + +custom_generator.h +------------------- + +.. doxygenclass:: buildcc::CustomGenerator + +Example +-------- + +> TODO, Update example + +.. code-block:: cpp + :linenos: + + // Example Setup + TargetEnv gen_env("gen_root", "gen_build"); + bool parallel{true}; + BaseGenerator generator("unique_name", gen_env, parallel); + // Adds absolute {gen_root_dir} and {gen_build_dir} paths to internal fmt::format + + // We can now do this + // {gen_root_dir}/my_generator.py -> {my_generator} + generator.AddInput("{gen_root_dir}/my_generator.py", "my_generator"); + + // {gen_build_dir}/my_generator.h -> {generated_header} + // {gen_build_dir}/my_generator.cpp -> {generated_source} + generator.AddOutput("{gen_build_dir}/generated.h", "generated_header"); + generator.AddOutput("{gen_build_dir}/generated.cpp", "generated_source"); + + // Example Commands + // NOTE: If `parallel==true`, both of these commands runs in parallel + // NOTE: If `parallel==false`, commands will run in the order that they are added. + generator.AddCommand("{my_generator} --generate header {generated_header}"); + generator.AddCommand("{my_generator} --generate source {generated_source}"); + + // Setup the tasks + generator.Build(); + + // Run the generator + tf::Executor executor; + executor.run(generator.GetTaskflow()); + executor.wait_for_all(); diff --git a/docs/source/user_api/register.rst b/docs/source/user_api/register.rst new file mode 100644 index 00000000..4d8d5795 --- /dev/null +++ b/docs/source/user_api/register.rst @@ -0,0 +1,49 @@ +Register +========= + +register.h +----------- + +.. doxygenclass:: buildcc::Reg + +test_info.h +------------- + +.. doxygenstruct:: buildcc::TestConfig + +.. doxygenstruct:: buildcc::TestOutput + +Example +-------- + +.. code-block:: cpp + :linenos: + + class BigObj {}; + + static void callback_usage_func(const BigObj & cobj, BigObj & obj); + + int main(int argc, char ** argv) { + Args::Init() + .Parse(argc, argv); + + Reg::Init(); + Reg::Call(Args::Clean()).Func([](){ + fs::remove_all(Project::GetBuildDir()); + }) + + + BigObj obj; + Reg::Call().Func(callback_usage_func, BigObj(), obj) + + bool expression = true; // false + Reg::Call(expression).Func(callback_usage_func, BigObj(), obj) + + // Example snippets of these given in Target API + // Build + // Dep + // Test + // RunBuild + // RunTest + return 0; + } diff --git a/docs/source/user_api/supported_plugins.rst b/docs/source/user_api/supported_plugins.rst new file mode 100644 index 00000000..87204809 --- /dev/null +++ b/docs/source/user_api/supported_plugins.rst @@ -0,0 +1,21 @@ +Supported Plugins +================= + +clang_compile_commands.h +------------------------ + +.. doxygenclass:: buildcc::plugin::ClangCompileCommands + +Example +-------- + +.. code-block:: cpp + :linenos: + + using namespace buildcc; + + Target foolib; + Target hello_world; + + // Foolib and Hello world targets are both added to a single "compile_commands.json" file + plugin::ClangCompileCommands({&foolib, &hello_world}).Generate(); diff --git a/docs/source/user_api/target.rst b/docs/source/user_api/target.rst new file mode 100644 index 00000000..5d7fa001 --- /dev/null +++ b/docs/source/user_api/target.rst @@ -0,0 +1,127 @@ +Target Info APIs +================= + +source_api.h +------------- + +.. doxygenclass:: buildcc::internal::SourceApi + +include_api.h +--------------- + +.. doxygenclass:: buildcc::internal::IncludeApi + +lib_api.h +---------- + +.. doxygenclass:: buildcc::internal::LibApi + +pch_api.h +----------- + +.. doxygenclass:: buildcc::internal::PchApi + +flag_api.h +------------ + +.. doxygenclass:: buildcc::internal::FlagApi + +deps_api.h +----------- + +.. doxygenclass:: buildcc::internal::DepsApi + +sync_api.h +------------ + +.. doxygenclass:: buildcc::internal::SyncApi + +TargetInfo +=========== + +target_info.h +-------------- + +.. doxygenclass:: buildcc::TargetInfo + +.. doxygentypedef:: BaseTargetInfo + +Target APIs +============= + +.. important:: Target APIs can also use TargetInfo APIs + +target_env.h +--------------------- + +.. doxygenclass:: buildcc::internal::TargetEnvApi + +Target +======= + +target.h +--------- + +.. doxygenclass:: buildcc::Target + +.. doxygentypedef:: buildcc::BaseTarget + +Specialized Target +================== + +target_custom.h +--------------- + +.. doxygentypedef:: buildcc::Target_custom + +target_gcc.h +------------- + +.. doxygenclass:: buildcc::ExecutableTarget_gcc + +.. doxygenclass:: buildcc::StaticTarget_gcc + +.. doxygenclass:: buildcc::DynamicTarget_gcc + +target_msvc.h +------------- + +.. doxygenclass:: buildcc::ExecutableTarget_msvc + +.. doxygenclass:: buildcc::StaticTarget_msvc + +.. doxygenclass:: buildcc::DynamicTarget_msvc + +target_generic.h +----------------- + +.. doxygenclass:: buildcc::ExecutableTarget_generic + +.. doxygenclass:: buildcc::StaticTarget_generic + +.. doxygenclass:: buildcc::DynamicTarget_generic + +.. doxygenclass:: buildcc::Target_generic + +Example +-------- + +.. code-block:: cpp + :linenos: + + // Generic toolchain GCC + Toolchain_gcc gcc; + + // Target compatible GCC toolchain + ExecutableTarget_gcc hello_world("name", gcc, "relative_to_global_env_root_dir"); + + // NOTE: See APIs above, they are self explanatory + hello_world.AddSource(""); + + // Setup the tasks + hello_world.Build(); + + // Run the task + tf::Executor executor; + executor.run(hello_world.GetTaskflow()); + executor.wait_for_all(); diff --git a/docs/source/user_api/target_utils.rst b/docs/source/user_api/target_utils.rst new file mode 100644 index 00000000..4940883f --- /dev/null +++ b/docs/source/user_api/target_utils.rst @@ -0,0 +1,24 @@ +Target Utils +============= + +Commonly used Utils (Classes / Structs) for **Generator**, **TargetInfo** and **Target** classes. + +target_type.h +-------------- + +.. doxygenenum:: buildcc::TargetType + +target_env.h +-------------- + +.. doxygenclass:: buildcc::TargetEnv + +target_config.h +---------------- + +.. doxygenstruct:: buildcc::TargetConfig + +target_state.h +--------------- + +.. doxygenstruct:: buildcc::TargetState diff --git a/docs/source/user_api/toc.rst b/docs/source/user_api/toc.rst new file mode 100644 index 00000000..c7c858d8 --- /dev/null +++ b/docs/source/user_api/toc.rst @@ -0,0 +1,15 @@ +User API +========== + + +.. toctree:: + + args + register + toolchain_utils + toolchain + target_utils + generator + target + supported_plugins + environment diff --git a/docs/source/user_api/toolchain.rst b/docs/source/user_api/toolchain.rst new file mode 100644 index 00000000..debbe45d --- /dev/null +++ b/docs/source/user_api/toolchain.rst @@ -0,0 +1,114 @@ +Toolchain +========= + +toolchain.h +------------ + +.. doxygenclass:: buildcc::Toolchain + +.. doxygentypedef:: BaseToolchain + +toolchain_find.h +----------------- + +.. doxygenclass:: buildcc::ToolchainFind + +.. doxygenstruct:: buildcc::ToolchainFindConfig + +toolchain_verify.h +------------------ + +.. doxygenclass:: buildcc::ToolchainVerify + +.. doxygenstruct:: buildcc::ToolchainCompilerInfo + +Example for Default Toolchain +------------------------------ + +.. code-block:: cpp + :linenos: + + BaseToolchain arm_gcc(ToolchainId::Gcc, "arm-none-eabi-gcc", "arm-none-eabi-as", "arm-none-eabi-gcc", "arm-none-eabi-g++", "arm-none-eabi-ar", "arm-none-eabi-ld"); + + // Toolchain::Find is only used to return a list of paths where the ToolchainExecutables are found + // NOTE: All ToolchainExecutables must be found in a single directory for it to be present in the list + { + ToolchainFindConfig find_config; + // Modify it here if needed + auto found_toolchains = arm_gcc.Find(find_config); + } + + // Runs Toolchain::Find + // Selects first found toolchain (update ToolchainVerifyConfig if you want to select a different toolchain for verification) + // Runs a pre-added ToolchainId::GCC verification function + // If Verification Fails: Terminates the program + // Else: Updates the arm_gcc ToolchainExecutables to the full path + // i.e `arm-none-eabi-gcc` becomes `{host_absolute_path}/arm-none-eabi-gcc{host_executable_extension}` + { + ToolchainVerifyConfig verify_config; + // Modify it here if needed + arm_gcc.Verify(verify_config); + } + +Example for Custom Toolchain +---------------------------- + +.. code-block:: cpp + :linenos: + + BaseToolchain custom_toolchain(ToolchainId::Custom, "custom_new_toolchain", "assembler", "c_compiler", "cpp_compiler", "archiver", "linker"); + + // Toolchain::Find similar to previous example + + // Find all the relevant toolchains on your host system + // Selects the first found toolchain + // Runs a verification function on the selected toolchain depending on the `ToolchainId` + Toolchain::AddVerificationFunc(ToolchainId::Custom, + [](const ToolchainExecutables & executables) -> buildcc::env::optional { + // Use executables to get compiler_version and target_arch + if (success) { + ToolchainCompilerInfo info; + info.compiler_version = "compiler_version"; + info.target_arch = "target_arch"; + return info; + } else { + return {}; + } + }, "custom_verification_func") + + ToolchainVerifyConfig verify_config; + verify_config.verification_identifier = "custom_verification_func"; + custom_toolchain.Verify(verify_config); + +Specialized Toolchain +===================== + +toolchain_gcc.h +---------------- + +.. doxygenclass:: buildcc::Toolchain_gcc + +toolchain_mingw.h +----------------- + +.. doxygenclass:: buildcc::Toolchain_mingw + +toolchain_msvc.h +----------------- + +.. doxygenclass:: buildcc::Toolchain_msvc + +Example +-------- + +.. code-block:: cpp + :linenos: + + // Default GCC toolchain + Toolchain_gcc gcc; + + // Default MinGW toolchain + Toolchain_mingw mingw; + + // Default MSVC toolchain + Toolchain_msvc msvc; diff --git a/docs/source/user_api/toolchain_utils.rst b/docs/source/user_api/toolchain_utils.rst new file mode 100644 index 00000000..b3161529 --- /dev/null +++ b/docs/source/user_api/toolchain_utils.rst @@ -0,0 +1,24 @@ +Toolchain Utils +================ + +Commonly used Utils (Classes / Structs) for **Toolchain** + +file_ext.h +------------ + +.. doxygenenum:: buildcc::FileExt + +toolchain_config.h +-------------------- + +.. doxygenstruct:: buildcc::ToolchainConfig + +toolchain_id.h +--------------- + +.. doxygenenum:: buildcc::ToolchainId + +toolchain_executables.h +----------------------- + +.. doxygenstruct:: buildcc::ToolchainExecutables diff --git a/example/README.md b/example/README.md index 48bb75d8..277a78b0 100644 --- a/example/README.md +++ b/example/README.md @@ -62,9 +62,7 @@ Multi hosts but only one target compiler used > NOTE, See the distinction between **HOST** and **TARGET** -## [host] MinGW GCC/GNU -> [target] gcc -## [host] MSVC -> [target] gcc -## [host] Linux GCC/GNU -> [target] gcc +## [target] Gcc - [x] Simple - Only 1 source file @@ -84,7 +82,7 @@ Multi hosts but only one target compiler used - [ ] ClangFormat - [ ] Taskflow graph visualizer -## [host] MSVC -> [target] MSVC +## [target] MSVC > TODO, Understand how MSVC compilation using `cl.exe` occurs @@ -95,6 +93,12 @@ Multi hosts but only one target compiler used - [x] DynamicLib - MSVC DynamicLib + Executable +## [target] MinGW + +- [x] Executable +- [x] StaticLib +- [x] DynamicLib + # Real world Tests (Hybrid) Multi hosts and multi targets @@ -106,6 +110,10 @@ Multi hosts and multi targets - Tested with **host** windows and linux with **host compilers** gcc, clang and msvc to generate **hybrid targets** using different compilers i.e gcc, clang and msvc +**Current state of examples** + +- [x] Single + - Compile a single source with `Register` and `Args` module - [x] Simple - Similar to Flags example with `Register` and `Args` module - [x] Foolib @@ -114,5 +122,15 @@ Multi hosts and multi targets - For end users consuming third party libraries - [x] Custom Target - For super customized targets and toolchains +- [x] Generic + - Select library type and target-toolchain type at runtime +- [x] PCH + - Precompile header usage with GCC and MSVC compilers +- [x] Dependency Chaining + - Chain `Generators` and `Targets` using the `Register` module +- [x] Target Info + - Target Info usage to store Target specific information + - Example usage for Header Only targets, however it can store information for all Target inputs + - Common information used between multiple targets can be stored into a `TargetInfo` instance - [ ] Debugging - [ ] Cross Compilation with Unit Testing diff --git a/example/buildexe/libs/build.main.cpp b/example/buildexe/libs/build.main.cpp new file mode 100644 index 00000000..64301e32 --- /dev/null +++ b/example/buildexe/libs/build.main.cpp @@ -0,0 +1,72 @@ +#include "buildcc.h" + +// Included through libs +#include "build.fmt.h" + +// Generated by BuildCC +// See the `_build_internal` directory +#include "buildexe_lib_dirs.h" + +using namespace buildcc; + +constexpr const char *const EXE = "build"; + +// Function Prototypes +static void clean_cb(); +static void hello_world_build_cb(BaseTarget &target, BaseTarget &fmt_lib); + +int main(int argc, char **argv) { + // Get arguments + ArgToolchain arg_gcc; + Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc) + .Parse(argc, argv); + + // Initialize your environment + Reg::Init(); + + // Pre-build steps + Reg::Call(Args::Clean()).Func(clean_cb); + + // Build steps + // Explicit toolchain - target pairs + Toolchain_gcc gcc; + + StaticTarget_gcc fmt_lib( + "libfmt", gcc, + TargetEnv(BuildExeLibDir::fmt, Project::GetBuildDir() / "fmt")); + FmtConfig fmt_config; + ExecutableTarget_gcc hello_world("hello_world", gcc, ""); + Reg::Toolchain(arg_gcc.state) + .Func([&]() { gcc.Verify(); }) + .Build(build_fmt_cb, fmt_lib, fmt_config) + .Build(hello_world_build_cb, hello_world, fmt_lib) + .Dep(hello_world, fmt_lib) + .Test("{executable}", hello_world); + + // Build Target + Reg::Run(); + + // Post Build steps + // - Clang Compile Commands + plugin::ClangCompileCommands({&hello_world}).Generate(); + // - Graphviz dump + std::cout << Reg::GetTaskflow().dump() << std::endl; + + return 0; +} + +static void clean_cb() { + env::log_info(EXE, fmt::format("Cleaning {}", Project::GetBuildDir())); + fs::remove_all(Project::GetBuildDir()); +} + +static void hello_world_build_cb(BaseTarget &target, BaseTarget &fmt_lib) { + target.AddSource("main.cpp", "src"); + target.AddLibDep(fmt_lib); + target.Insert(fmt_lib, { + SyncOption::IncludeDirs, + SyncOption::HeaderFiles, + }); + target.Build(); +} diff --git a/example/buildexe/libs/build.toml b/example/buildexe/libs/build.toml new file mode 100644 index 00000000..45233263 --- /dev/null +++ b/example/buildexe/libs/build.toml @@ -0,0 +1,10 @@ +# Root +root_dir = "" +build_dir = "_build" +loglevel = "debug" +clean = true + +# Toolchain +[toolchain.gcc] +build = true +test = true diff --git a/example/buildexe/libs/compile.toml b/example/buildexe/libs/compile.toml new file mode 100644 index 00000000..db93b6cd --- /dev/null +++ b/example/buildexe/libs/compile.toml @@ -0,0 +1,20 @@ +# Settings +root_dir = "" +build_dir = "_build_internal" +loglevel = "debug" +clean = false + +# BuildExe run mode +mode = "script" + +# Target information +name = "single" +type = "executable" +relative_to_root = "" +srcs = ["build.main.cpp"] + +[script] +configs = ["build.toml"] + +[libs] +fmt = ["build.fmt.cpp", "build.fmt.h"] diff --git a/example/buildexe/libs/src/main.cpp b/example/buildexe/libs/src/main.cpp new file mode 100644 index 00000000..4a638183 --- /dev/null +++ b/example/buildexe/libs/src/main.cpp @@ -0,0 +1,6 @@ +#include "fmt/format.h" + +int main() { + fmt::print("{} {}", "Hello", "World"); + return 0; +} diff --git a/example/gcc/AfterInstall/CMakeLists.txt b/example/gcc/AfterInstall/CMakeLists.txt index fb08813f..55b3aca4 100644 --- a/example/gcc/AfterInstall/CMakeLists.txt +++ b/example/gcc/AfterInstall/CMakeLists.txt @@ -12,35 +12,22 @@ project(simple) # fmt is imported by spdlog by default find_package(fmt_package NAMES "fmt" REQUIRED) find_package(spdlog_package NAMES "spdlog" REQUIRED) -find_package(flatbuffers_package NAMES "Flatbuffers" "flatbuffers" REQUIRED) +find_package(nlohmann_json_package NAMES "nlohmann_json" REQUIRED) find_package(taskflow_package NAMES "Taskflow" "taskflow" REQUIRED) +find_package(tl_optional_package NAMES "tl-optional" REQUIRED) find_package(CLI11_package NAMES "CLI11" REQUIRED) find_package(tiny_process_library_package NAMES "tiny-process-library" REQUIRED) -find_package(env_package NAMES "env" REQUIRED) -find_package(toolchain_package NAMES "toolchain" REQUIRED) -find_package(toolchain_specialized_package NAMES "toolchain_specialized" REQUIRED) -find_package(target_package NAMES "target" REQUIRED) -find_package(target_gcc_package NAMES "target_gcc" REQUIRED) -find_package(target_msvc_package NAMES "target_msvc" REQUIRED) -find_package(plugins NAMES "plugins" REQUIRED) - find_package(buildcc_package NAMES "buildcc" REQUIRED) message("Find package: ${fmt_package_DIR}") message("Find package: ${spdlog_package_DIR}") -message("Find package: ${flatbuffers_package_DIR}") +message("Find package: ${nlohmann_json_package_DIR}") +message("Find package: ${tl_optional_package_DIR}") message("Find package: ${taskflow_package_DIR}") message("Find package: ${CLI11_package_DIR}") -message("Find package: ${tiny_process_library_package_DIR}") - -message("Find package: ${env_package_DIR}") -message("Find package: ${toolchain_package_DIR}") -message("Find package: ${toolchain_specialized_package_DIR}") -message("Find package: ${target_package_DIR}") -message("Find package: ${target_gcc_package_DIR}") -message("Find package: ${target_msvc_package_DIR}") -message("Find package: ${plugins_DIR}") +message("Find package: ${tiny_process_library_package_DIR}") # + message("Find package: ${buildcc_package_DIR}") # build executable diff --git a/example/gcc/AfterInstall/build.cpp b/example/gcc/AfterInstall/build.cpp index 40e01260..1c7a08ca 100644 --- a/example/gcc/AfterInstall/build.cpp +++ b/example/gcc/AfterInstall/build.cpp @@ -12,14 +12,13 @@ int main(void) { // this path // 2. Intermediate build folder i.e all intermediate generated files should go // here - env::init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); + Project::Init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); env::set_log_level(env::LogLevel::Trace); - // Stored as a const & in target - base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", "ld"); + Toolchain_gcc gcc; // CppTarget - ExecutableTarget_gcc cpptarget("CppFlags.exe", gcc, "files"); + ExecutableTarget_gcc cpptarget("CppFlags", gcc, "files"); cpptarget.AddSource("main.cpp", "src"); cpptarget.AddSource("src/random.cpp"); cpptarget.AddHeader("include/random.h"); @@ -31,7 +30,7 @@ int main(void) { cpptarget.Build(); // CTarget - ExecutableTarget_gcc ctarget("CFlags.exe", gcc, "files"); + ExecutableTarget_gcc ctarget("CFlags", gcc, "files"); ctarget.AddSource("main.c", "src"); ctarget.AddPreprocessorFlag("-DRANDOM=1"); ctarget.AddCCompileFlag("-Wall"); diff --git a/example/gcc/DynamicLib/build.cpp b/example/gcc/DynamicLib/build.cpp index dcc59e6b..7f7e4056 100644 --- a/example/gcc/DynamicLib/build.cpp +++ b/example/gcc/DynamicLib/build.cpp @@ -6,95 +6,54 @@ using namespace buildcc; -#define LINUX 1 -#define WINDOWS 0 - -#define OS WINDOWS - -// This example contains both OS Hosts -// - Windows MSYS GCC 10.2.0 // - Linux GCC 9.3.0 -// * Comment out one of the examples int main(void) { // Environment is meant to define // 1. Project Root path i.e all files and targets will be added relative to // this path // 2. Intermediate build folder i.e all intermediate generated files should go // here - env::init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); + Project::Init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); env::set_log_level(env::LogLevel::Trace); - base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", "ld"); + Toolchain_gcc gcc; -// Linux GCC -#if OS == LINUX - DynamicTarget_gcc randomDynLib("librandyn.so", gcc, "files"); - ExecutableTarget_gcc target("dynamictest.exe", gcc, "files"); - randomDynLib.AddSource("src/random.cpp"); - randomDynLib.AddHeader("include/random.h"); - randomDynLib.AddIncludeDir("include"); - randomDynLib.Build(); + DynamicTarget_gcc dynamictarget("librandom", gcc, "files"); + dynamictarget.AddSource("src/random.cpp"); + dynamictarget.AddHeader("include/random.h"); + dynamictarget.AddIncludeDir("include"); + dynamictarget.Build(); + ExecutableTarget_gcc target("dynamictest", gcc, "files"); target.AddSource("main.cpp", "src"); target.AddIncludeDir("include"); // * Method 1 // NOTE, Use buildcc built targets - // target.AddLibDep(randomDynLib); + target.AddLibDep(dynamictarget); // * Method 2, External lib - target.AddLibDirAbsolute(randomDynLib.GetTargetIntermediateDir()); - target.AddLibDep("-lrandyn"); - target.AddLinkFlag("-Wl,-rpath=" + - randomDynLib.GetTargetIntermediateDir().string()); + // target.AddLibDirAbsolute(dynamictarget.GetTargetBuildDir()); + // target.AddLibDep("-lrandom"); target.Build(); -#endif - -// MingW GCC -#if OS == WINDOWS - DynamicTarget_gcc randomDynLib("librandyn.dll", gcc, "files"); - ExecutableTarget_gcc target("dynamictest.exe", gcc, "files"); - - randomDynLib.AddSource("src/random.cpp"); - randomDynLib.AddHeader("include/random.h"); - randomDynLib.AddIncludeDir("include"); - randomDynLib.Build(); - - target.AddSource("main.cpp", "src"); - target.AddIncludeDir("include"); - - // * Method 1 - // NOTE, Use buildcc built targets - // target.AddLibDep(randomDynLib); - - // * Method 2, External lib - target.AddLibDirAbsolute(randomDynLib.GetTargetIntermediateDir()); - target.AddLibDep("-lrandyn"); - - target.Build(); - -#endif tf::Executor executor; tf::Taskflow taskflow; - auto randomDynLibTask = taskflow.composed_of(randomDynLib.GetTaskflow()); + auto dynamictargetTask = taskflow.composed_of(dynamictarget.GetTaskflow()); auto targetTask = taskflow.composed_of(target.GetTaskflow()); - targetTask.succeed(randomDynLibTask); + targetTask.succeed(dynamictargetTask); executor.run(taskflow); executor.wait_for_all(); -// Post Build step -#if OS == WINDOWS - if (target.FirstBuild() || target.Rebuild()) { + // Post Build step + if (target.IsBuilt()) { fs::path copy_to_path = - target.GetTargetIntermediateDir() / randomDynLib.GetName(); - fs::remove(copy_to_path); - fs::copy(randomDynLib.GetTargetPath(), copy_to_path); + target.GetTargetBuildDir() / dynamictarget.GetTargetPath().filename(); + fs::copy(dynamictarget.GetTargetPath(), copy_to_path); } -#endif // Dump .dot output taskflow.dump(std::cout); diff --git a/example/gcc/Flags/build.cpp b/example/gcc/Flags/build.cpp index 8428f59e..01a412b6 100644 --- a/example/gcc/Flags/build.cpp +++ b/example/gcc/Flags/build.cpp @@ -12,21 +12,17 @@ int main(void) { // this path // 2. Intermediate build folder i.e all intermediate generated files should go // here - env::init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); + Project::Init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); env::set_log_level(env::LogLevel::Trace); - // Stored as a const & in target - base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", "ld"); + Toolchain_gcc gcc; // CppTarget - ExecutableTarget_gcc cpptarget("CppFlags.exe", gcc, "files"); - + ExecutableTarget_gcc cpptarget("CppFlags", gcc, "files"); cpptarget.AddSource("main.cpp", "src"); cpptarget.AddSource("src/random.cpp"); - cpptarget.AddHeader("include/random.h"); cpptarget.AddIncludeDir("include"); - cpptarget.AddPreprocessorFlag("-DRANDOM=1"); cpptarget.AddCppCompileFlag("-Wall"); cpptarget.AddCppCompileFlag("-Werror"); @@ -34,7 +30,7 @@ int main(void) { cpptarget.Build(); // CTarget - ExecutableTarget_gcc ctarget("CFlags.exe", gcc, "files"); + ExecutableTarget_gcc ctarget("CFlags", gcc, "files"); ctarget.AddSource("main.c", "src"); ctarget.AddPreprocessorFlag("-DRANDOM=1"); ctarget.AddCCompileFlag("-Wall"); @@ -42,6 +38,7 @@ int main(void) { ctarget.AddLinkFlag("-lm"); ctarget.Build(); + // Run tf::Executor executor; tf::Taskflow taskflow; taskflow.composed_of(cpptarget.GetTaskflow()); diff --git a/example/gcc/IncludeDir/build.cpp b/example/gcc/IncludeDir/build.cpp index 027f7b6f..1a64262a 100644 --- a/example/gcc/IncludeDir/build.cpp +++ b/example/gcc/IncludeDir/build.cpp @@ -12,13 +12,12 @@ int main(void) { // this path // 2. Intermediate build folder i.e all intermediate generated files should go // here - env::init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); + Project::Init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); env::set_log_level(env::LogLevel::Trace); - // Stored as const & in target - base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", "ld"); + Toolchain_gcc gcc; - ExecutableTarget_gcc target("IncludeDir.exe", gcc, "files"); + ExecutableTarget_gcc target("IncludeDir", gcc, "files"); target.AddSource("main.cpp", "src"); target.AddSource("src/random.cpp"); target.AddHeader("include/random.h"); diff --git a/example/gcc/Plugins/build.cpp b/example/gcc/Plugins/build.cpp index 5757174a..1c324a56 100644 --- a/example/gcc/Plugins/build.cpp +++ b/example/gcc/Plugins/build.cpp @@ -4,8 +4,6 @@ #include "buildcc.h" #include "constants.h" -#include "clang_compile_commands.h" - using namespace buildcc; int main(void) { @@ -14,13 +12,13 @@ int main(void) { // this path // 2. Intermediate build folder i.e all intermediate generated files should go // here - env::init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); + Project::Init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); env::set_log_level(env::LogLevel::Trace); - // CppTarget - base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", "ld"); - ExecutableTarget_gcc cppflags("CppFlags.exe", gcc, "files"); + Toolchain_gcc gcc; + // CppTarget + ExecutableTarget_gcc cppflags("CppFlags", gcc, "files"); cppflags.AddSource("main.cpp", "src"); cppflags.AddSource("src/random.cpp"); cppflags.AddHeader("include/random.h"); @@ -32,7 +30,7 @@ int main(void) { cppflags.Build(); // CTarget - ExecutableTarget_gcc cflags("CFlags.exe", gcc, "files"); + ExecutableTarget_gcc cflags("CFlags", gcc, "files"); cflags.AddSource("main.c", "src"); cflags.AddPreprocessorFlag("-DRANDOM=1"); cflags.AddCCompileFlag("-Wall"); @@ -48,8 +46,7 @@ int main(void) { executor.wait_for_all(); // Clang compile command db test - plugin::ClangCompileCommands compile_commands({&cppflags, &cflags}); - compile_commands.Generate(); + plugin::ClangCompileCommands({&cppflags, &cflags}).Generate(); // Dot Dump // TODO, Make this into a plugin diff --git a/example/gcc/Simple/build.cpp b/example/gcc/Simple/build.cpp index 05570d5b..04686bd3 100644 --- a/example/gcc/Simple/build.cpp +++ b/example/gcc/Simple/build.cpp @@ -12,13 +12,12 @@ int main(void) { // this path // 2. Intermediate build folder i.e all intermediate generated files should go // here - env::init(BUILD_SCRIPT_SOURCE, BUILD_SCRIPT_FOLDER); + Project::Init(BUILD_SCRIPT_SOURCE, BUILD_SCRIPT_FOLDER); env::set_log_level(env::LogLevel::Trace); - // Stored as const & in target - base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", "ld"); + Toolchain_gcc gcc; - ExecutableTarget_gcc target("Simple.exe", gcc, ""); + ExecutableTarget_gcc target("Simple", gcc, ""); target.AddSource("main.cpp"); target.Build(); diff --git a/example/gcc/StaticLib/build.cpp b/example/gcc/StaticLib/build.cpp index 55f5c526..163264f8 100644 --- a/example/gcc/StaticLib/build.cpp +++ b/example/gcc/StaticLib/build.cpp @@ -12,42 +12,42 @@ int main(void) { // this path // 2. Intermediate build folder i.e all intermediate generated files should go // here - env::init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); + Project::Init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); env::set_log_level(env::LogLevel::Trace); - base::Toolchain gcc("gcc", "as", "gcc", "g++", "ar", "ld"); + Toolchain_gcc gcc; - StaticTarget_gcc randomLib("libran.a", gcc, "files"); - randomLib.AddSource("src/random.cpp"); - randomLib.AddHeader("include/random.h"); - randomLib.AddIncludeDir("include"); - randomLib.Build(); + StaticTarget_gcc statictarget("librandom", gcc, "files"); + statictarget.AddSource("src/random.cpp"); + statictarget.AddHeader("include/random.h"); + statictarget.AddIncludeDir("include"); + statictarget.Build(); - ExecutableTarget_gcc target("statictest.exe", gcc, "files"); - target.AddSource("main.cpp", "src"); - target.AddIncludeDir("include"); + ExecutableTarget_gcc exetarget("statictest", gcc, "files"); + exetarget.AddSource("main.cpp", "src"); + exetarget.AddIncludeDir("include"); // * Method 1 // NOTE, Use buildcc built targets - // target.AddLibDep(randomLib); + exetarget.AddLibDep(statictarget); // * Method 2 // NOTE, This should be an absolute path since we could also be referencing // external libs that are not relative to this project - target.AddLibDirAbsolute(randomLib.GetTargetPath().parent_path()); - target.AddLibDep("-lran"); + // exetarget.AddLibDirAbsolute(statictarget.GetTargetPath().parent_path()); + // exetarget.AddLibDep("-lrandom"); - target.Build(); + exetarget.Build(); // Run tf::Executor executor; tf::Taskflow taskflow; - tf::Task randomLibTask = taskflow.composed_of(randomLib.GetTaskflow()); - tf::Task targetTask = taskflow.composed_of(target.GetTaskflow()); + tf::Task statictargetTask = taskflow.composed_of(statictarget.GetTaskflow()); + tf::Task exetargetTask = taskflow.composed_of(exetarget.GetTaskflow()); // Set dependency - targetTask.succeed(randomLibTask); + exetargetTask.succeed(statictargetTask); // Run executor.run(taskflow); diff --git a/example/hybrid/custom_target/build.main.cpp b/example/hybrid/custom_target/build.main.cpp index 943f546a..88ece332 100644 --- a/example/hybrid/custom_target/build.main.cpp +++ b/example/hybrid/custom_target/build.main.cpp @@ -1,127 +1,62 @@ #include "buildcc.h" -#include "clang_compile_commands.h" - using namespace buildcc; static void clean_cb(); -static void gfoolib_build_cb(base::Target &target); -static void mfoolib_build_cb(base::Target &target); -static void cfoolib_build_cb(base::Target &target); - -// * NOTE, This is how we add our custom target -// Support for any custom toolchain can be added like this -// base::Target provides `CompileCommand` and `Link` overrides -class ExecutableTarget_clang : public base::Target { -public: - ExecutableTarget_clang( - const std::string &name, const base::Toolchain &toolchain, - const std::filesystem::path &target_path_relative_to_root) - : Target(name, base::TargetType::Executable, toolchain, - target_path_relative_to_root) {} - -private: -private: - // Compiling - virtual std::vector - CompileCommand(const std::string &input_source, - const std::string &output_source, const std::string &compiler, - const std::string &aggregated_preprocessor_flags, - const std::string &aggregated_compile_flags, - const std::string &aggregated_include_dirs) const override { - return { - compiler, - aggregated_preprocessor_flags, - aggregated_include_dirs, - aggregated_compile_flags, - "-o", - output_source, - "-c", - input_source, - }; - } +static void foolib_build_cb(BaseTarget &target); - // Linking - virtual std::vector - Link(const std::string &output_target, - const std::string &aggregated_link_flags, - const std::string &aggregated_compiled_sources, - const std::string &aggregated_lib_dirs, - const std::string &aggregated_lib_deps) const override { - return { - GetToolchain().GetCppCompiler(), - aggregated_link_flags, - aggregated_compiled_sources, - "-o", - output_target, - aggregated_lib_dirs, - aggregated_lib_deps, - }; - } -}; +static constexpr std::string_view EXE = "build"; int main(int argc, char **argv) { - // 1. Get arguments - Args args; - Args::Toolchain clang_gnu; - args.AddCustomToolchain("clang_gnu", "Clang GNU compiler", clang_gnu); - args.Parse(argc, argv); - - // 2. Initialize your environment - Register reg(args); - reg.Env(); - - // 3. Pre-build steps - reg.Clean(clean_cb); - - // 4. Build steps + // Get arguments + ArgToolchain arg_gcc; + ArgToolchain arg_msvc; + ArgToolchain arg_toolchain_clang_gnu; + ArgTarget arg_target_clang_gnu; + Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc) + .AddToolchain("msvc", "Generic msvc toolchain", arg_msvc) + .AddToolchain("clang_gnu", "Clang GNU toolchain", arg_toolchain_clang_gnu) + .AddTarget("clang_gnu", "Clang GNU target", arg_target_clang_gnu) + .Parse(argc, argv); + + // Initialize your environment + Reg::Init(); + + // Pre-build steps + Reg::Call(Args::Clean()).Func(clean_cb); + + // Build steps Toolchain_gcc gcc; - Toolchain_msvc msvc; - - ExecutableTarget_gcc g_foolib("GFoolib.exe", gcc, ""); - ExecutableTarget_msvc m_foolib("MFoolib.exe", msvc, ""); + ExecutableTarget_gcc g_foolib("foolib", gcc, ""); + Reg::Toolchain(arg_gcc.state).Build(foolib_build_cb, g_foolib); - reg.Build(args.GetGccToolchain(), g_foolib, gfoolib_build_cb); - reg.Build(args.GetMsvcToolchain(), m_foolib, mfoolib_build_cb); + Toolchain_msvc msvc; + ExecutableTarget_msvc m_foolib("foolib", msvc, ""); + Reg::Toolchain(arg_msvc.state).Build(foolib_build_cb, m_foolib); // * NOTE, This is how we add our custom toolchain - base::Toolchain clang("clang_gnu", "llvm-as", "clang", "clang++", "llvm-ar", - "ld"); - // * NOTE, Custom clang target added above - ExecutableTarget_clang c_foolib("CFoolib.exe", clang, ""); - reg.Build(clang_gnu, c_foolib, cfoolib_build_cb); + auto &clang = arg_toolchain_clang_gnu.ConstructToolchain(); + Target_custom c_foolib("CFoolib.exe", TargetType::Executable, clang, "", + arg_target_clang_gnu.GetTargetConfig()); + Reg::Toolchain(arg_toolchain_clang_gnu.state) + .Build(foolib_build_cb, c_foolib); - // 5. - reg.RunBuild(); + // + Reg::Run(); - // 6. + // plugin::ClangCompileCommands({&g_foolib, &m_foolib, &c_foolib}).Generate(); return 0; } static void clean_cb() { - // TODO, -} - -static void gfoolib_build_cb(base::Target &target) { - target.AddSource("src/foo.cpp"); - target.AddIncludeDir("src", true); - target.AddSource("main.cpp"); - target.Build(); -} - -static void mfoolib_build_cb(base::Target &target) { - target.AddCppCompileFlag("/nologo"); - target.AddCppCompileFlag("/EHsc"); - target.AddLinkFlag("/nologo"); - target.AddSource("src/foo.cpp"); - target.AddIncludeDir("src", true); - target.AddSource("main.cpp"); - target.Build(); + env::log_info(EXE, fmt::format("Cleaning {}", Project::GetBuildDir())); + fs::remove_all(Project::GetBuildDir()); } -static void cfoolib_build_cb(base::Target &target) { +static void foolib_build_cb(BaseTarget &target) { target.AddSource("src/foo.cpp"); target.AddIncludeDir("src", true); target.AddSource("main.cpp"); diff --git a/example/hybrid/custom_target/build_linux.toml b/example/hybrid/custom_target/build_linux.toml index b7f7bd87..4d61ad2f 100644 --- a/example/hybrid/custom_target/build_linux.toml +++ b/example/hybrid/custom_target/build_linux.toml @@ -19,3 +19,16 @@ test = false [toolchain.clang_gnu] build = true test = true + +id = "Clang" +name = "clang_gnu" +asm_compiler = "llvm-as" +c_compiler = "clang" +cpp_compiler = "clang++" +archiver = "llvm-ar" +linker = "ld" + +# Custom target added here +[target.clang_gnu] +compile_command = "{compiler} {preprocessor_flags} {include_dirs} {compile_flags} -o {output} -c {input}" +link_command = "{cpp_compiler} {link_flags} {compiled_sources} -o {output} {lib_dirs} {lib_deps}" diff --git a/example/hybrid/custom_target/build_win.toml b/example/hybrid/custom_target/build_win.toml index 602f0d01..ec789d2c 100644 --- a/example/hybrid/custom_target/build_win.toml +++ b/example/hybrid/custom_target/build_win.toml @@ -19,3 +19,16 @@ test = true [toolchain.clang_gnu] build = true test = true + +id = "Clang" +name = "clang_gnu" +asm_compiler = "llvm-as" +c_compiler = "clang" +cpp_compiler = "clang++" +archiver = "llvm-ar" +linker = "ld" + +# Custom target added here +[target.clang_gnu] +compile_command = "{compiler} {preprocessor_flags} {include_dirs} {compile_flags} -o {output} -c {input}" +link_command = "{cpp_compiler} {link_flags} {compiled_sources} -o {output} {lib_dirs} {lib_deps}" diff --git a/example/hybrid/dep_chaining/.gitignore b/example/hybrid/dep_chaining/.gitignore new file mode 100644 index 00000000..cb283a9c --- /dev/null +++ b/example/hybrid/dep_chaining/.gitignore @@ -0,0 +1,10 @@ +# Folder +generated +buildcc +_internal* + +# Files +*.exe +*.o +*.bin +*.dot diff --git a/example/hybrid/dep_chaining/CMakeLists.txt b/example/hybrid/dep_chaining/CMakeLists.txt new file mode 100644 index 00000000..392e2c07 --- /dev/null +++ b/example/hybrid/dep_chaining/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.10.0) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +project(hybrid_depchaining_example) + +# Bootstrap your build file using CMake +add_executable(hybrid_depchaining_example build.cpp) +target_link_libraries(hybrid_depchaining_example PRIVATE buildcc) + +# TODO, Add this only if MINGW is used +# https://github.com/msys2/MINGW-packages/issues/2303 +# Similar issue when adding the Taskflow library +if (${MINGW}) + message(WARNING "-Wl,--allow-multiple-definition for MINGW") + target_link_options(hybrid_depchaining_example PRIVATE -Wl,--allow-multiple-definition) +endif() + +# Run your build file +add_custom_target(run_hybrid_depchaining_example_win + COMMAND hybrid_depchaining_example --help-all + COMMAND hybrid_depchaining_example --config ${CMAKE_CURRENT_SOURCE_DIR}/build_win.toml + # COMMAND dot -Tpng graph.dot -o graph.PNG + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS hybrid_depchaining_example buildcc + VERBATIM USES_TERMINAL +) + +add_custom_target(run_hybrid_depchaining_example_linux + COMMAND hybrid_depchaining_example --help-all + COMMAND hybrid_depchaining_example --config ${CMAKE_CURRENT_SOURCE_DIR}/build_linux.toml + # COMMAND dot -Tpng graph.dot -o graph.PNG + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS hybrid_depchaining_example buildcc + VERBATIM USES_TERMINAL +) diff --git a/example/hybrid/dep_chaining/build.cpp b/example/hybrid/dep_chaining/build.cpp new file mode 100644 index 00000000..1195f989 --- /dev/null +++ b/example/hybrid/dep_chaining/build.cpp @@ -0,0 +1,115 @@ +#include "buildcc.h" + +#include "fmt/format.h" + +using namespace buildcc; + +constexpr const char *const EXE = "build"; + +// Function Prototypes +static void clean_cb(); +static void cpp_target_cb(BaseTarget &cpptarget, + const FileGenerator &cpp_generator); +static void c_target_cb(BaseTarget &ctarget, const FileGenerator &c_generator); +static void cpp_generator_cb(FileGenerator &generator); +static void c_generator_cb(FileGenerator &generator); + +int main(int argc, char **argv) { + // Get arguments + ArgToolchain arg_gcc; + ArgToolchain arg_msvc; + Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc) + .AddToolchain("msvc", "Generic msvc toolchain", arg_msvc) + .Parse(argc, argv); + + // Initialize your environment + Reg::Init(); + + // Pre-build steps + Reg::Call(Args::Clean()).Func(clean_cb); + + // Generator + FileGenerator cpp_generator("cpp_generator", ""); + FileGenerator c_generator("c_generator", ""); + Reg::Call() + .Build(cpp_generator_cb, cpp_generator) + .Build(c_generator_cb, c_generator); + + // Build steps + // Explicit toolchain - target pairs + Toolchain_gcc gcc; + ExecutableTarget_gcc g_cpptarget("cpptarget", gcc, ""); + ExecutableTarget_gcc g_ctarget("ctarget", gcc, ""); + Reg::Toolchain(arg_gcc.state) + .Build(cpp_target_cb, g_cpptarget, cpp_generator) + .Build(c_target_cb, g_ctarget, c_generator) + .Dep(g_cpptarget, cpp_generator) + .Dep(g_ctarget, c_generator) + .Test("{executable}", g_cpptarget) + .Test("{executable}", g_ctarget); + + Toolchain_msvc msvc; + ExecutableTarget_msvc m_cpptarget("cpptarget", msvc, ""); + ExecutableTarget_msvc m_ctarget("ctarget", msvc, ""); + Reg::Toolchain(arg_msvc.state) + .Build(cpp_target_cb, m_cpptarget, cpp_generator) + .Build(c_target_cb, m_ctarget, c_generator) + .Dep(m_cpptarget, cpp_generator) + .Dep(m_ctarget, c_generator) + .Test("{executable}", m_cpptarget) + .Test("{executable}", m_ctarget); + + // Build and Test + Reg::Run(); + + // - Clang Compile Commands + plugin::ClangCompileCommands( + {&g_cpptarget, &m_cpptarget, &g_ctarget, &m_ctarget}) + .Generate(); + + // - Plugin Graph + std::string output = Reg::GetTaskflow().dump(); + const bool saved = env::save_file("graph.dot", output, false); + env::assert_fatal(saved, "Could not save graph.dot file"); + + return 0; +} + +static void clean_cb() { + env::log_info(EXE, fmt::format("Cleaning {}", Project::GetBuildDir())); + fs::remove_all(Project::GetBuildDir()); +} + +static void cpp_target_cb(BaseTarget &cpptarget, + const FileGenerator &cpp_generator) { + const fs::path main_cpp = fs::path(cpp_generator.Get("main_cpp")) + .lexically_relative(Project::GetRootDir()); + cpptarget.AddSource(main_cpp); + cpptarget.Build(); +} + +static void c_target_cb(BaseTarget &ctarget, const FileGenerator &c_generator) { + const fs::path main_c = fs::path(c_generator.Get("main_c")) + .lexically_relative(Project::GetRootDir()); + ctarget.AddSource(main_c); + ctarget.Build(); +} + +static void cpp_generator_cb(FileGenerator &generator) { + generator.AddPattern("main_cpp", "{current_build_dir}/main.cpp"); + generator.AddOutput("{main_cpp}"); + generator.AddCommand( + "python3 {current_root_dir}/python/gen.py --source_type cpp " + "--destination {main_cpp}"); + generator.Build(); +} + +static void c_generator_cb(FileGenerator &generator) { + generator.AddPattern("main_c", "{current_build_dir}/main.c"); + generator.AddOutput("{main_c}"); + generator.AddCommand( + "python3 {current_root_dir}/python/gen.py --source_type c " + "--destination {main_c}"); + generator.Build(); +} diff --git a/example/hybrid/dep_chaining/build_linux.toml b/example/hybrid/dep_chaining/build_linux.toml new file mode 100644 index 00000000..25353efc --- /dev/null +++ b/example/hybrid/dep_chaining/build_linux.toml @@ -0,0 +1,16 @@ +# Root +root_dir = "" +build_dir = "_internal_linux" +loglevel = "trace" + +# Project +clean = true + +# Toolchain +[toolchain.gcc] +build = true +test = true + +[toolchain.msvc] +build = false +test = false diff --git a/example/hybrid/dep_chaining/build_win.toml b/example/hybrid/dep_chaining/build_win.toml new file mode 100644 index 00000000..1a49112d --- /dev/null +++ b/example/hybrid/dep_chaining/build_win.toml @@ -0,0 +1,16 @@ +# Root +root_dir = "" +build_dir = "_internal_win" +loglevel = "trace" + +# Project +clean = true + +# Toolchain +[toolchain.gcc] +build = true +test = true + +[toolchain.msvc] +build = true +test = true diff --git a/example/hybrid/dep_chaining/graph.PNG b/example/hybrid/dep_chaining/graph.PNG new file mode 100644 index 00000000..709f4559 Binary files /dev/null and b/example/hybrid/dep_chaining/graph.PNG differ diff --git a/example/hybrid/dep_chaining/python/gen.py b/example/hybrid/dep_chaining/python/gen.py new file mode 100644 index 00000000..6473bc9c --- /dev/null +++ b/example/hybrid/dep_chaining/python/gen.py @@ -0,0 +1,38 @@ +import argparse + +def cli_arguments(): + parser = argparse.ArgumentParser(description="Generates a .c / .cpp source file at provided destination location") + parser.add_argument('--source_type', required=True, choices=["c", "cpp"]) + parser.add_argument('--destination', required=True) + args = parser.parse_args() + print("Source Type", args.source_type) + print("Destination", args.destination) + + return (args.source_type, args.destination) + +# Run +source_type, destination = cli_arguments() +file = open(destination, "w") + +source = "" +if source_type == "c": + source = """ + #include + + int main() { + printf("Hello World from C"); + return 0; + } + """ +elif source_type == "cpp": + source = """ + #include + + int main() { + std::cout << "Hello World from CPP" << std::endl; + return 0; + } + """ + +file.write(source) +file.close() diff --git a/example/hybrid/external_lib/build.main.cpp b/example/hybrid/external_lib/build.main.cpp index 84511d3b..c2361b9e 100644 --- a/example/hybrid/external_lib/build.main.cpp +++ b/example/hybrid/external_lib/build.main.cpp @@ -2,60 +2,63 @@ #include "build.foo.h" -#include "clang_compile_commands.h" - using namespace buildcc; static void clean_cb(); -static void gfoolib_build_cb(base::Target &target); -static void mfoolib_build_cb(base::Target &target); +static void foolib_build_cb(BaseTarget &target, const TargetInfo &foolib); + +constexpr const char *const EXE = "build"; int main(int argc, char **argv) { - // 1. Get arguments - Args args; - args.Parse(argc, argv); + // Get arguments + ArgToolchain arg_gcc; + ArgToolchain arg_msvc; + Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc) + .AddToolchain("msvc", "Generic msvc toolchain", arg_msvc) + .Parse(argc, argv); - // 2. Initialize your environment - Register reg(args); - reg.Env(); + // Initialize your environment + Reg::Init(); - // 3. Pre-build steps - reg.Clean(clean_cb); + // Pre-build steps + Reg::Call(Args::Clean()).Func(clean_cb); - // 4. Build steps + // Build steps Toolchain_gcc gcc; - Toolchain_msvc msvc; + TargetInfo g_foo(gcc, "../foolib"); + ExecutableTarget_gcc g_external("cppflags", gcc, ""); + Reg::Toolchain(arg_gcc.state) + .Func(fooTarget, g_foo) + .Build(foolib_build_cb, g_external, g_foo); - ExecutableTarget_gcc g_foolib("GCppFlags.exe", gcc, ""); - ExecutableTarget_msvc m_foolib("MCppFlags.exe", msvc, ""); - - reg.Build(args.GetGccToolchain(), g_foolib, gfoolib_build_cb); - reg.Build(args.GetMsvcToolchain(), m_foolib, mfoolib_build_cb); + Toolchain_msvc msvc; + ExecutableTarget_msvc m_external("cppflags", msvc, ""); + TargetInfo m_foo(gcc, "../foolib"); + Reg::Toolchain(arg_msvc.state) + .Func(fooTarget, m_foo) + .Build(foolib_build_cb, m_external, m_foo); - // 5. - reg.RunBuild(); + // + Reg::Run(); - // 6. - plugin::ClangCompileCommands({&g_foolib, &m_foolib}).Generate(); + // + plugin::ClangCompileCommands({&g_external, &m_external}).Generate(); return 0; } static void clean_cb() { - // TODO, -} - -static void gfoolib_build_cb(base::Target &target) { - fooTarget(target, "../foolib"); - target.AddSource("main.cpp"); - target.Build(); + env::log_info(EXE, fmt::format("Cleaning {}", Project::GetBuildDir())); + fs::remove_all(Project::GetBuildDir()); } -static void mfoolib_build_cb(base::Target &target) { - target.AddCppCompileFlag("/nologo"); - target.AddCppCompileFlag("/EHsc"); - target.AddLinkFlag("/nologo"); - fooTarget(target, "../foolib"); +static void foolib_build_cb(BaseTarget &target, const TargetInfo &foolib) { target.AddSource("main.cpp"); + target.Insert(foolib, { + SyncOption::SourceFiles, + SyncOption::HeaderFiles, + SyncOption::IncludeDirs, + }); target.Build(); } diff --git a/example/hybrid/foolib/build.foo.cpp b/example/hybrid/foolib/build.foo.cpp index 447428cf..56142aeb 100644 --- a/example/hybrid/foolib/build.foo.cpp +++ b/example/hybrid/foolib/build.foo.cpp @@ -1,6 +1,6 @@ #include "build.foo.h" -void fooTarget(buildcc::base::Target &target, const fs::path &relative_path) { - target.AddSource(relative_path / "src/foo.cpp"); - target.AddIncludeDir(relative_path / "src", true); +void fooTarget(buildcc::TargetInfo &target) { + target.AddSource("src/foo.cpp"); + target.AddIncludeDir("src", true); } diff --git a/example/hybrid/foolib/build.foo.h b/example/hybrid/foolib/build.foo.h index c0dde091..4a741df6 100644 --- a/example/hybrid/foolib/build.foo.h +++ b/example/hybrid/foolib/build.foo.h @@ -2,4 +2,4 @@ #include "buildcc.h" -void fooTarget(buildcc::base::Target &target, const fs::path &relative_path); +void fooTarget(buildcc::TargetInfo &target); diff --git a/example/hybrid/foolib/build.main.cpp b/example/hybrid/foolib/build.main.cpp index ef02d87d..58e104e9 100644 --- a/example/hybrid/foolib/build.main.cpp +++ b/example/hybrid/foolib/build.main.cpp @@ -2,60 +2,63 @@ #include "build.foo.h" -#include "clang_compile_commands.h" - using namespace buildcc; static void clean_cb(); -static void gfoolib_build_cb(base::Target &target); -static void mfoolib_build_cb(base::Target &target); +static void main_build_cb(BaseTarget &target, const TargetInfo &foolib); + +constexpr std::string_view EXE = "build"; int main(int argc, char **argv) { - // 1. Get arguments - Args args; - args.Parse(argc, argv); + // Get arguments + ArgToolchain arg_gcc; + ArgToolchain arg_msvc; + Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc) + .AddToolchain("msvc", "Generic msvc toolchain", arg_msvc) + .Parse(argc, argv); - // 2. Initialize your environment - Register reg(args); - reg.Env(); + // Initialize your environment + Reg::Init(); - // 3. Pre-build steps - reg.Clean(clean_cb); + // Pre-build steps + Reg::Call(Args::Clean()).Func(clean_cb); - // 4. Build steps + // Build steps Toolchain_gcc gcc; - Toolchain_msvc msvc; + TargetInfo g_foo(gcc, ""); + ExecutableTarget_gcc g_main("one_executable", gcc, ""); + Reg::Toolchain(arg_gcc.state) + .Func(fooTarget, g_foo) + .Build(main_build_cb, g_main, g_foo); - ExecutableTarget_gcc g_foolib("GFoolib.exe", gcc, ""); - ExecutableTarget_msvc m_foolib("MFoolib.exe", msvc, ""); - - reg.Build(args.GetGccToolchain(), g_foolib, gfoolib_build_cb); - reg.Build(args.GetMsvcToolchain(), m_foolib, mfoolib_build_cb); + Toolchain_msvc msvc; + TargetInfo m_foo(msvc, ""); + ExecutableTarget_msvc m_main("one_executable", msvc, ""); + Reg::Toolchain(arg_msvc.state) + .Func(fooTarget, m_foo) + .Build(main_build_cb, m_main, m_foo); - // 5. - reg.RunBuild(); + // + Reg::Run(); - // 6. - plugin::ClangCompileCommands({&g_foolib, &m_foolib}).Generate(); + // + plugin::ClangCompileCommands({&g_main, &m_main}).Generate(); return 0; } static void clean_cb() { - // TODO, -} - -static void gfoolib_build_cb(base::Target &target) { - fooTarget(target, ""); - target.AddSource("main.cpp"); - target.Build(); + env::log_info(EXE, fmt::format("Cleaning {}", Project::GetBuildDir())); + fs::remove_all(Project::GetBuildDir()); } -static void mfoolib_build_cb(base::Target &target) { - target.AddCppCompileFlag("/nologo"); - target.AddCppCompileFlag("/EHsc"); - target.AddLinkFlag("/nologo"); - fooTarget(target, ""); +static void main_build_cb(BaseTarget &target, const TargetInfo &foolib) { target.AddSource("main.cpp"); + target.Insert(foolib, { + SyncOption::SourceFiles, + SyncOption::HeaderFiles, + SyncOption::IncludeDirs, + }); target.Build(); } diff --git a/example/hybrid/foolib/src/foo.cpp b/example/hybrid/foolib/src/foo.cpp index dfe450dd..0ada8bdb 100644 --- a/example/hybrid/foolib/src/foo.cpp +++ b/example/hybrid/foolib/src/foo.cpp @@ -2,14 +2,14 @@ #include -void vFoo() { std::cout << __FUNCTION__ << std::endl; } +EXPORT void vFoo() { std::cout << __FUNCTION__ << std::endl; } -int iFoo() { +EXPORT int iFoo() { std::cout << __FUNCTION__ << std::endl; return 11; } -float fFoo(int integer) { +EXPORT float fFoo(int integer) { std::cout << __FUNCTION__ << std::endl; return (integer * 1.1); } diff --git a/example/hybrid/foolib/src/foo.h b/example/hybrid/foolib/src/foo.h index ac833f35..5322cf31 100644 --- a/example/hybrid/foolib/src/foo.h +++ b/example/hybrid/foolib/src/foo.h @@ -1,5 +1,12 @@ #pragma once -void vFoo(); -int iFoo(); -float fFoo(int integer); +#ifdef _WIN32 +#include +#define EXPORT __declspec(dllexport) +#else +#define EXPORT +#endif + +EXPORT void vFoo(); +EXPORT int iFoo(); +EXPORT float fFoo(int integer); diff --git a/example/hybrid/generic/.gitignore b/example/hybrid/generic/.gitignore new file mode 100644 index 00000000..f1ba52ad --- /dev/null +++ b/example/hybrid/generic/.gitignore @@ -0,0 +1,6 @@ +# File +*.dot +*.PNG + +# Folder +*_internal_* diff --git a/example/hybrid/generic/CMakeLists.txt b/example/hybrid/generic/CMakeLists.txt new file mode 100644 index 00000000..8695177f --- /dev/null +++ b/example/hybrid/generic/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.10.0) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +project(hybrid_generic_example) + +# Bootstrap your build file using CMake +add_executable(hybrid_generic_example + build.cpp + ../foolib/build.foo.cpp + ../foolib/build.foo.h +) +target_include_directories(hybrid_generic_example PRIVATE ../foolib) +target_link_libraries(hybrid_generic_example PRIVATE buildcc) + +# TODO, Add this only if MINGW is used +# https://github.com/msys2/MINGW-packages/issues/2303 +# Similar issue when adding the Taskflow library +if (${MINGW}) + message(WARNING "-Wl,--allow-multiple-definition for MINGW") + target_link_options(hybrid_generic_example PRIVATE -Wl,--allow-multiple-definition) +endif() + +# Run your build file +add_custom_target(run_hybrid_generic_example + COMMAND hybrid_generic_example --config ${CMAKE_CURRENT_SOURCE_DIR}/build_generic.toml + # COMMAND dot -Tpng graph.dot -o graph.PNG + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS hybrid_generic_example buildcc + VERBATIM USES_TERMINAL +) diff --git a/example/hybrid/generic/build.cpp b/example/hybrid/generic/build.cpp new file mode 100644 index 00000000..fd388c36 --- /dev/null +++ b/example/hybrid/generic/build.cpp @@ -0,0 +1,131 @@ +#include + +// Core build lib +#include "buildcc.h" + +// Third party-libs +#include "fmt/format.h" + +// Libraries +#include "build.foo.h" + +using namespace buildcc; + +constexpr std::string_view EXE = "build"; + +// Function Prototypes +static void clean_cb(); +static void args_lib_type_cb(CLI::App &app, TargetType &lib_type); +static void foolib_build_cb(BaseTarget &foolib_target); +static void generic_build_cb(BaseTarget &generic_target, + BaseTarget &foolib_target); + +static void post_build_cb(BaseTarget &generic_target, + BaseTarget &foolib_target); + +int main(int argc, char **argv) { + // Get arguments + ArgToolchain custom_toolchain; + TargetType default_lib_type{TargetType::StaticLibrary}; + Args::Init() + .AddToolchain("user", "User defined toolchain", custom_toolchain) + .AddCustomCallback( + [&](CLI::App &app) { args_lib_type_cb(app, default_lib_type); }) + .Parse(argc, argv); + + // Initialize your environment + Reg::Init(); + + // Pre-build steps + Reg::Call(Args::Clean()).Func(clean_cb); + + // Build steps + // Toolchain + Generic Target + auto &toolchain = custom_toolchain.ConstructToolchain(); + Target_generic foolib_target("libfoo", default_lib_type, toolchain, + "../foolib"); + Target_generic generic_target("generic", TargetType::Executable, toolchain, + "src"); + Reg::Toolchain(custom_toolchain.state) + .Func([&]() { toolchain.Verify(); }) + .Build(foolib_build_cb, foolib_target) + .Build(generic_build_cb, generic_target, foolib_target) + .Dep(generic_target, foolib_target) + .Test("{executable}", generic_target); + + // Build Target + Reg::Run([&]() { post_build_cb(generic_target, foolib_target); }); + + // - Clang Compile Commands + plugin::ClangCompileCommands({&foolib_target, &generic_target}).Generate(); + + // - Plugin Graph + std::string output = Reg::GetTaskflow().dump(); + const bool saved = env::save_file("graph.dot", output, false); + env::assert_fatal(saved, "Could not save graph.dot file"); + + return 0; +} + +static void clean_cb() { + env::log_info(EXE, fmt::format("Cleaning {}", Project::GetBuildDir())); + fs::remove_all(Project::GetBuildDir()); +} + +void args_lib_type_cb(CLI::App &app, TargetType &lib_type) { + const std::map lib_type_map_{ + {"StaticLib", TargetType::StaticLibrary}, + {"DynamicLib", TargetType::DynamicLibrary}, + }; + + app.add_option("--default_lib_type", lib_type, "Default Lib Type") + ->transform(CLI::CheckedTransformer(lib_type_map_, CLI::ignore_case)) + ->group("Custom"); +} + +static void foolib_build_cb(BaseTarget &foolib_target) { + fooTarget(foolib_target); + foolib_target.Build(); +} + +static void generic_build_cb(BaseTarget &generic_target, + BaseTarget &foolib_target) { + generic_target.AddSource("main.cpp"); + generic_target.AddLibDep(foolib_target); + generic_target.Insert(foolib_target, { + SyncOption::IncludeDirs, + }); + generic_target.Build(); +} + +void post_build_cb(BaseTarget &generic_target, BaseTarget &foolib_target) { + // For Static Lib do nothing + // For Dynamic Lib we need to handle special cases + // - MSVC behaviour + // - Copy to executable location + if (foolib_target.GetType() == TargetType::DynamicLibrary) { + // MSVC special case + fs::path copy_from_path; + fs::path copy_to_path; + if (foolib_target.GetToolchain().GetId() == ToolchainId::Msvc) { + copy_from_path = + fmt::format("{}.dll", path_as_string(foolib_target.GetTargetPath())); + copy_to_path = + generic_target.GetTargetBuildDir() / + fmt::format("{}.dll", + foolib_target.GetTargetPath().filename().string()); + } else { + copy_from_path = foolib_target.GetTargetPath(); + copy_to_path = + generic_target.GetTargetBuildDir() / + (foolib_target.GetName() + foolib_target.GetConfig().target_ext); + } + + // Copy case + // TODO, This should be baked into the `Target` API + if (generic_target.IsBuilt()) { + fs::remove(copy_to_path); + fs::copy(copy_from_path, copy_to_path); + } + } +} diff --git a/example/hybrid/generic/build_generic.toml b/example/hybrid/generic/build_generic.toml new file mode 100644 index 00000000..970ee8bd --- /dev/null +++ b/example/hybrid/generic/build_generic.toml @@ -0,0 +1,30 @@ +# Root +root_dir = "" +build_dir = "_internal_generic" +loglevel = "trace" + +# Project +clean = true + +# Custom +default_lib_type = "DynamicLib" + +[toolchain.user] +build = true +test = true + +id = "Gcc" +name = "gcc" +asm_compiler = "as" +c_compiler = "gcc" +cpp_compiler = "g++" +archiver = "ar" +linker = "ld" + +# id = "Msvc" +# name = "msvc" +# asm_compiler = "cl" +# c_compiler = "cl" +# cpp_compiler = "cl" +# archiver = "lib" +# linker = "link" diff --git a/example/hybrid/generic/src/main.cpp b/example/hybrid/generic/src/main.cpp new file mode 100644 index 00000000..f4677a30 --- /dev/null +++ b/example/hybrid/generic/src/main.cpp @@ -0,0 +1,11 @@ +#include + +#include "foo.h" + +int main() { + std::cout << "Hello World" << std::endl; + vFoo(); + std::cout << iFoo() << std::endl; + std::cout << fFoo(11) << std::endl; + return 0; +} diff --git a/example/hybrid/pch/.gitignore b/example/hybrid/pch/.gitignore new file mode 100644 index 00000000..cb283a9c --- /dev/null +++ b/example/hybrid/pch/.gitignore @@ -0,0 +1,10 @@ +# Folder +generated +buildcc +_internal* + +# Files +*.exe +*.o +*.bin +*.dot diff --git a/example/hybrid/pch/CMakeLists.txt b/example/hybrid/pch/CMakeLists.txt new file mode 100644 index 00000000..ed9a5076 --- /dev/null +++ b/example/hybrid/pch/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.10.0) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +project(hybrid_pch_example) + +# Bootstrap your build file using CMake +add_executable(hybrid_pch_example build.cpp) +target_link_libraries(hybrid_pch_example PRIVATE buildcc) + +# TODO, Add this only if MINGW is used +# https://github.com/msys2/MINGW-packages/issues/2303 +# Similar issue when adding the Taskflow library +if (${MINGW}) + message(WARNING "-Wl,--allow-multiple-definition for MINGW") + target_link_options(hybrid_pch_example PRIVATE -Wl,--allow-multiple-definition) +endif() + +# Run your build file +add_custom_target(run_hybrid_pch_example_linux + COMMAND hybrid_pch_example --help-all + COMMAND hybrid_pch_example --config ${CMAKE_CURRENT_SOURCE_DIR}/build_linux.toml + # COMMAND dot -Tpng graph.dot -o graph.PNG + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS hybrid_pch_example buildcc + VERBATIM USES_TERMINAL +) + +# TODO, MSVC support +add_custom_target(run_hybrid_pch_example_win + COMMAND hybrid_pch_example --help-all + COMMAND hybrid_pch_example --config ${CMAKE_CURRENT_SOURCE_DIR}/build_win.toml + # COMMAND dot -Tpng graph.dot -o graph.PNG + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS hybrid_pch_example buildcc + VERBATIM USES_TERMINAL +) diff --git a/example/hybrid/pch/build.cpp b/example/hybrid/pch/build.cpp new file mode 100644 index 00000000..28f3d410 --- /dev/null +++ b/example/hybrid/pch/build.cpp @@ -0,0 +1,126 @@ +#include "buildcc.h" + +#include "fmt/format.h" + +using namespace buildcc; + +constexpr const char *const EXE = "build"; + +// Function Prototypes +static void clean_cb(); +static void cppflags_build_cb(BaseTarget &cppflags); +static void cflags_build_cb(BaseTarget &cflags); + +int main(int argc, char **argv) { + // Get arguments + ArgToolchain arg_gcc; + ArgToolchain arg_msvc; + Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc) + .AddToolchain("msvc", "Generic msvc toolchain", arg_msvc) + .Parse(argc, argv); + + // Initialize your environment + Reg::Init(); + + // Pre-build steps + Reg::Call(Args::Clean()).Func(clean_cb); + + // Build steps + // Explicit toolchain - target pairs + Toolchain_gcc gcc; + ExecutableTarget_gcc g_cppflags("cppflags", gcc, "files"); + ExecutableTarget_gcc g_cflags("cflags", gcc, "files"); + + Reg::Toolchain(arg_gcc.state) + .Build(cppflags_build_cb, g_cppflags) + .Build(cflags_build_cb, g_cflags) + .Test("{executable}", g_cppflags) + .Test("{executable}", g_cflags); + + Toolchain_msvc msvc; + ExecutableTarget_msvc m_cppflags("cppflags", msvc, "files"); + ExecutableTarget_msvc m_cflags("cflags", msvc, "files"); + Reg::Toolchain(arg_msvc.state) + .Build(cppflags_build_cb, m_cppflags) + .Build(cflags_build_cb, m_cflags) + .Test("{executable}", m_cppflags) + .Test("{executable}", m_cflags); + + Reg::Run(); + + // Post Build steps + + // - Clang Compile Commands + plugin::ClangCompileCommands({&g_cflags, &g_cppflags}).Generate(); + + // - Plugin Graph + std::string output = Reg::GetTaskflow().dump(); + const bool saved = env::save_file("graph.dot", output, false); + env::assert_fatal(saved, "Could not save graph.dot file"); + + return 0; +} + +static void clean_cb() { + env::log_info(EXE, fmt::format("Cleaning {}", Project::GetBuildDir())); + fs::remove_all(Project::GetBuildDir()); +} + +static void cppflags_build_cb(BaseTarget &cppflags) { + cppflags.AddSource("main.cpp", "src"); + cppflags.AddSource("random.cpp", "src"); + cppflags.AddIncludeDir("include", true); + + cppflags.AddPch("pch/pch_cpp.h"); + cppflags.AddPch("pch/pch_c.h"); + cppflags.AddIncludeDir("pch", true); + + // Toolchain specific code goes here + switch (cppflags.GetToolchain().GetId()) { + case ToolchainId::Gcc: { + cppflags.AddPreprocessorFlag("-DRANDOM=1"); + cppflags.AddCppCompileFlag("-Wall"); + cppflags.AddCppCompileFlag("-Werror"); + cppflags.AddLinkFlag("-lm"); + break; + } + case ToolchainId::Msvc: { + cppflags.AddPreprocessorFlag("/DRANDOM=1"); + cppflags.AddCppCompileFlag("/W4"); + break; + } + default: + break; + } + + cppflags.Build(); +} + +static void cflags_build_cb(BaseTarget &cflags) { + cflags.AddSource("main.c", "src"); + + cflags.AddPch("pch/pch_c.h"); + cflags.AddIncludeDir("pch", false); + cflags.AddHeader("pch/pch_c.h"); + + // Toolchain specific code goes here + switch (cflags.GetToolchain().GetId()) { + case ToolchainId::Gcc: { + cflags.AddPreprocessorFlag("-DRANDOM=1"); + cflags.AddCCompileFlag("-Wall"); + cflags.AddCCompileFlag("-Werror"); + cflags.AddLinkFlag("-lm"); + break; + } + case ToolchainId::Msvc: { + cflags.AddPreprocessorFlag("/DRANDOM=1"); + cflags.AddCCompileFlag("/W4"); + break; + } + default: + break; + } + + cflags.Build(); +} diff --git a/example/hybrid/pch/build_linux.toml b/example/hybrid/pch/build_linux.toml new file mode 100644 index 00000000..a18f5c22 --- /dev/null +++ b/example/hybrid/pch/build_linux.toml @@ -0,0 +1,12 @@ +# Root +root_dir = "" +build_dir = "_internal_linux" +loglevel = "trace" + +# Project +clean = true + +# Toolchain +[toolchain.gcc] +build = true +test = true diff --git a/example/hybrid/pch/build_win.toml b/example/hybrid/pch/build_win.toml new file mode 100644 index 00000000..1a49112d --- /dev/null +++ b/example/hybrid/pch/build_win.toml @@ -0,0 +1,16 @@ +# Root +root_dir = "" +build_dir = "_internal_win" +loglevel = "trace" + +# Project +clean = true + +# Toolchain +[toolchain.gcc] +build = true +test = true + +[toolchain.msvc] +build = true +test = true diff --git a/example/hybrid/pch/files/include/random.h b/example/hybrid/pch/files/include/random.h new file mode 100644 index 00000000..6aa3357d --- /dev/null +++ b/example/hybrid/pch/files/include/random.h @@ -0,0 +1,3 @@ +#pragma once + +void random_print(); diff --git a/example/hybrid/pch/files/pch/pch_c.h b/example/hybrid/pch/files/pch/pch_c.h new file mode 100644 index 00000000..56d69117 --- /dev/null +++ b/example/hybrid/pch/files/pch/pch_c.h @@ -0,0 +1,7 @@ +#ifndef PCH_C_H_ +#define PCH_C_H_ + +#include +#include + +#endif diff --git a/example/hybrid/pch/files/pch/pch_cpp.h b/example/hybrid/pch/files/pch/pch_cpp.h new file mode 100644 index 00000000..54896970 --- /dev/null +++ b/example/hybrid/pch/files/pch/pch_cpp.h @@ -0,0 +1,9 @@ +#ifndef PCH_CPP_H_ +#define PCH_CPP_H_ + +#include +#include + +#include "random.h" + +#endif diff --git a/example/hybrid/pch/files/src/main.c b/example/hybrid/pch/files/src/main.c new file mode 100644 index 00000000..efbfc706 --- /dev/null +++ b/example/hybrid/pch/files/src/main.c @@ -0,0 +1,20 @@ +#include "pch_c.h" + +int main() { + printf("Hello World\r\n"); + + // -Wall -Werror throw error when unused variable + // int i = 0; + +#if RANDOM == 1 + printf("Random == 1\r\n"); +#endif + + float f1 = 10.2; + float f2 = 2.3; + + float value = pow(f1, f2); + printf("Computed pow: %f\r\n", value); + + return 0; +} diff --git a/example/hybrid/pch/files/src/main.cpp b/example/hybrid/pch/files/src/main.cpp new file mode 100644 index 00000000..9598d166 --- /dev/null +++ b/example/hybrid/pch/files/src/main.cpp @@ -0,0 +1,20 @@ +#include "pch_cpp.h" + +int main() { + std::cout << "Hello World from test" << std::endl; + + // -Wall -Werror flags throw error when unused variable + // int i = 0; + +#if RANDOM == 1 + random_print(); +#endif + + float f1 = 10.2; + float f2 = 2.3; + + float value = pow(f1, f2); + std::cout << value << std::endl; + + return 0; +} diff --git a/example/hybrid/pch/files/src/random.cpp b/example/hybrid/pch/files/src/random.cpp new file mode 100644 index 00000000..14559d96 --- /dev/null +++ b/example/hybrid/pch/files/src/random.cpp @@ -0,0 +1,3 @@ +#include "pch_cpp.h" + +void random_print() { std::cout << __FUNCTION__ << std::endl; } diff --git a/example/hybrid/pch/graph.PNG b/example/hybrid/pch/graph.PNG new file mode 100644 index 00000000..7cb95607 Binary files /dev/null and b/example/hybrid/pch/graph.PNG differ diff --git a/example/hybrid/simple/build.cpp b/example/hybrid/simple/build.cpp index d3ab8358..7dbacb3b 100644 --- a/example/hybrid/simple/build.cpp +++ b/example/hybrid/simple/build.cpp @@ -1,131 +1,120 @@ #include "buildcc.h" -#include "clang_compile_commands.h" #include "fmt/format.h" -#include "flatbuffers/util.h" - -#include "assert_fatal.h" - using namespace buildcc; constexpr const char *const EXE = "build"; // Function Prototypes static void clean_cb(); -static void gcppflags_build_cb(base::Target &g_cppflags); -static void gcflags_build_cb(base::Target &g_cflags); -static void mcppflags_build_cb(base::Target &m_cppflags); -static void mcflags_build_cb(base::Target &m_cflags); +static void cppflags_build_cb(BaseTarget &cppflags); +static void cflags_build_cb(BaseTarget &cflags); int main(int argc, char **argv) { - // 1. Get arguments - Args args; - args.Parse(argc, argv); - - // 2. Initialize your environment - Register reg(args); - reg.Env(); - - // 3. Pre-build steps - reg.Clean(clean_cb); - - // 4. Build steps + // Get arguments + ArgToolchain arg_gcc; + ArgToolchain arg_msvc; + Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc) + .AddToolchain("msvc", "Generic msvc toolchain", arg_msvc) + .Parse(argc, argv); + + // Initialize your environment + Reg::Init(); + + // Pre-build steps + Reg::Call(Args::Clean()).Func(clean_cb); + + // Build steps + // Explicit toolchain - target pairs Toolchain_gcc gcc; - Toolchain_msvc msvc; - - ExecutableTarget_gcc g_cppflags("GCppFlags.exe", gcc, "files"); - ExecutableTarget_gcc g_cflags("GCFlags.exe", gcc, "files"); - - ExecutableTarget_msvc m_cppflags("MCppFlags.exe", msvc, "files"); - ExecutableTarget_msvc m_cflags("MCFlags.exe", msvc, "files"); - - reg.Build(args.GetGccToolchain(), g_cppflags, gcppflags_build_cb); - reg.Build(args.GetGccToolchain(), g_cflags, gcflags_build_cb); - reg.Build(args.GetMsvcToolchain(), m_cppflags, mcppflags_build_cb); - reg.Build(args.GetMsvcToolchain(), m_cflags, mcflags_build_cb); - - // 5. Test steps + ExecutableTarget_gcc g_cppflags("cppflags", gcc, "files"); + ExecutableTarget_gcc g_cflags("cflags", gcc, "files"); + Reg::Toolchain(arg_gcc.state) + .Func([&]() { gcc.Verify(); }) + .Build(cppflags_build_cb, g_cppflags) + .Build(cflags_build_cb, g_cflags) + .Test("{executable}", g_cppflags) + .Test("{executable}", g_cflags); - // NOTE, For now they are just dummy callbacks - reg.Test(args.GetGccToolchain(), g_cppflags, [](base::Target &target) {}); - reg.Test(args.GetGccToolchain(), g_cflags, [](base::Target &target) {}); - reg.Test(args.GetMsvcToolchain(), m_cppflags, [](base::Target &target) {}); - reg.Test(args.GetMsvcToolchain(), m_cflags, [](base::Target &target) {}); - - // 6. Build Target - reg.RunBuild(); + Toolchain_msvc msvc; + ExecutableTarget_msvc m_cppflags("cppflags", msvc, "files"); + ExecutableTarget_msvc m_cflags("cflags", msvc, "files"); + Reg::Toolchain(arg_msvc.state) + .Func([&]() { msvc.Verify(); }) + .Build(cppflags_build_cb, m_cppflags) + .Build(cflags_build_cb, m_cflags) + .Test("{executable}", m_cppflags) + .Test("{executable}", m_cflags); - // 7. Test Target - reg.RunTest(); + Reg::Run(); - // 8. Post Build steps + // Post Build steps // - Clang Compile Commands plugin::ClangCompileCommands({&g_cflags, &g_cppflags, &m_cflags, &m_cppflags}) .Generate(); // - Plugin Graph - std::string output = reg.GetTaskflow().dump(); - const bool saved = flatbuffers::SaveFile("graph.dot", output, false); + std::string output = Reg::GetTaskflow().dump(); + const bool saved = env::save_file("graph.dot", output, false); env::assert_fatal(saved, "Could not save graph.dot file"); return 0; } static void clean_cb() { - env::log_info( - EXE, fmt::format("Cleaning {}", env::get_project_build_dir().string())); - fs::remove_all(env::get_project_build_dir()); -} - -static void gcppflags_build_cb(base::Target &g_cppflags) { - // GCC CppFlags - g_cppflags.AddSource("main.cpp", "src"); - g_cppflags.AddSource("src/random.cpp"); - - g_cppflags.AddHeader("include/random.h"); - g_cppflags.AddIncludeDir("include"); - - g_cppflags.AddPreprocessorFlag("-DRANDOM=1"); - g_cppflags.AddCppCompileFlag("-Wall"); - g_cppflags.AddCppCompileFlag("-Werror"); - g_cppflags.AddLinkFlag("-lm"); - g_cppflags.Build(); -} - -static void gcflags_build_cb(base::Target &g_cflags) { - // GCC CFlags - g_cflags.AddSource("main.c", "src"); - g_cflags.AddPreprocessorFlag("-DRANDOM=1"); - g_cflags.AddCCompileFlag("-Wall"); - g_cflags.AddCCompileFlag("-Werror"); - g_cflags.AddLinkFlag("-lm"); - g_cflags.Build(); + env::log_info(EXE, fmt::format("Cleaning {}", Project::GetBuildDir())); + fs::remove_all(Project::GetBuildDir()); } -static void mcppflags_build_cb(base::Target &m_cppflags) { - // GCC CppFlags - m_cppflags.AddSource("main.cpp", "src"); - m_cppflags.AddSource("src/random.cpp"); - - m_cppflags.AddHeader("include/random.h"); - m_cppflags.AddIncludeDir("include"); - - m_cppflags.AddPreprocessorFlag("/DRANDOM=1"); - m_cppflags.AddCppCompileFlag("/W4"); - m_cppflags.AddCppCompileFlag("/nologo"); - m_cppflags.AddCppCompileFlag("/EHsc"); - m_cppflags.AddLinkFlag("/nologo"); - m_cppflags.Build(); +static void cppflags_build_cb(BaseTarget &cppflags) { + cppflags.AddSource("main.cpp", "src"); + cppflags.AddSource("src/random.cpp"); + cppflags.AddIncludeDir("include", true); + + // Toolchain specific code goes here + switch (cppflags.GetToolchain().GetId()) { + case ToolchainId::Gcc: { + cppflags.AddPreprocessorFlag("-DRANDOM=1"); + cppflags.AddCppCompileFlag("-Wall"); + cppflags.AddCppCompileFlag("-Werror"); + cppflags.AddLinkFlag("-lm"); + break; + } + case ToolchainId::Msvc: { + cppflags.AddPreprocessorFlag("/DRANDOM=1"); + cppflags.AddCppCompileFlag("/W4"); + break; + } + default: + break; + } + + cppflags.Build(); } -static void mcflags_build_cb(base::Target &m_cflags) { - // GCC CFlags - m_cflags.AddSource("main.c", "src"); - m_cflags.AddPreprocessorFlag("/DRANDOM=1"); - m_cflags.AddCCompileFlag("/W4"); - m_cflags.AddCCompileFlag("/nologo"); - m_cflags.AddLinkFlag("/nologo"); - m_cflags.Build(); +static void cflags_build_cb(BaseTarget &cflags) { + cflags.AddSource("main.c", "src"); + + // Toolchain specific code goes here + switch (cflags.GetToolchain().GetId()) { + case ToolchainId::Gcc: { + cflags.AddPreprocessorFlag("-DRANDOM=1"); + cflags.AddCCompileFlag("-Wall"); + cflags.AddCCompileFlag("-Werror"); + cflags.AddLinkFlag("-lm"); + break; + } + case ToolchainId::Msvc: { + cflags.AddPreprocessorFlag("/DRANDOM=1"); + cflags.AddCCompileFlag("/W4"); + break; + } + default: + break; + } + + cflags.Build(); } diff --git a/example/hybrid/simple/files/src/main.c b/example/hybrid/simple/files/src/main.c index 090e9efe..4628975b 100644 --- a/example/hybrid/simple/files/src/main.c +++ b/example/hybrid/simple/files/src/main.c @@ -12,9 +12,7 @@ int main() { #endif float f1 = 10.2; - float f2 = 1.0; - printf("Input a floating point number\r\n"); - scanf("%f", &f2); + float f2 = 2.3; float value = pow(f1, f2); printf("Computed pow: %f\r\n", value); diff --git a/example/hybrid/simple/files/src/main.cpp b/example/hybrid/simple/files/src/main.cpp index 01820c25..f1523ef7 100644 --- a/example/hybrid/simple/files/src/main.cpp +++ b/example/hybrid/simple/files/src/main.cpp @@ -14,9 +14,7 @@ int main() { #endif float f1 = 10.2; - float f2 = 1.0; - std::cout << "input a floating point number" << std::endl; - std::cin >> f2; + float f2 = 2.3; float value = pow(f1, f2); std::cout << value << std::endl; diff --git a/example/hybrid/simple/graph.PNG b/example/hybrid/simple/graph.PNG index 89461353..a492995d 100644 Binary files a/example/hybrid/simple/graph.PNG and b/example/hybrid/simple/graph.PNG differ diff --git a/example/hybrid/single/.gitignore b/example/hybrid/single/.gitignore new file mode 100644 index 00000000..cb283a9c --- /dev/null +++ b/example/hybrid/single/.gitignore @@ -0,0 +1,10 @@ +# Folder +generated +buildcc +_internal* + +# Files +*.exe +*.o +*.bin +*.dot diff --git a/example/hybrid/single/CMakeLists.txt b/example/hybrid/single/CMakeLists.txt new file mode 100644 index 00000000..f5e60dca --- /dev/null +++ b/example/hybrid/single/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.10.0) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +project(hybrid_single_example) + +# Bootstrap your build file using CMake +add_executable(hybrid_single_example build.cpp) +target_link_libraries(hybrid_single_example PRIVATE buildcc) + +# TODO, Add this only if MINGW is used +# https://github.com/msys2/MINGW-packages/issues/2303 +# Similar issue when adding the Taskflow library +if (${MINGW}) + message(WARNING "-Wl,--allow-multiple-definition for MINGW") + target_link_options(hybrid_single_example PRIVATE -Wl,--allow-multiple-definition) +endif() + +# Run your build file +add_custom_target(run_hybrid_single_example + COMMAND hybrid_single_example --help-all + COMMAND hybrid_single_example --config ${CMAKE_CURRENT_SOURCE_DIR}/build.toml + # COMMAND dot -Tpng graph.dot -o graph.PNG + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS hybrid_single_example buildcc + VERBATIM USES_TERMINAL +) diff --git a/example/hybrid/single/README.md b/example/hybrid/single/README.md new file mode 100644 index 00000000..4c488dbc --- /dev/null +++ b/example/hybrid/single/README.md @@ -0,0 +1,19 @@ +# Single Example + +Running the Single example using BuildExe + +NOTE: Follow the **Getting Started** guide first + +## Script mode + +``` +buildexe --config compile.toml --config $BUILDCC_HOME/host/host_toolchain.toml +buildexe --config compile.toml --config %BUILDCC_HOME%/host/host_toolchain.toml +``` + +## Immediate mode + +``` +buildexe --config immediate.toml --config $BUILDCC_HOME/host/host_toolchain.toml +buildexe --config immediate.toml --config %BUILDCC_HOME%/host/host_toolchain.toml +``` diff --git a/example/hybrid/single/build.cpp b/example/hybrid/single/build.cpp new file mode 100644 index 00000000..b85a774f --- /dev/null +++ b/example/hybrid/single/build.cpp @@ -0,0 +1,54 @@ +#include "buildcc.h" + +using namespace buildcc; + +constexpr const char *const EXE = "build"; + +// Function Prototypes +static void clean_cb(); +static void hello_world_build_cb(BaseTarget &target); + +int main(int argc, char **argv) { + // Get arguments + ArgToolchain arg_gcc; + Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc) + .Parse(argc, argv); + + // Initialize your environment + Reg::Init(); + + // Pre-build steps + Reg::Call(Args::Clean()).Func(clean_cb); + + // Build steps + // Explicit toolchain - target pairs + Toolchain_gcc gcc; + ExecutableTarget_gcc hello_world("hello_world", gcc, ""); + + // Select your builds and tests using the .toml files + Reg::Toolchain(arg_gcc.state) + .Func([&]() { gcc.Verify(); }) + .Build(hello_world_build_cb, hello_world) + .Test("{executable}", hello_world); + + // Build and Test Target + Reg::Run(); + + // Post Build steps + // - Clang Compile Commands + plugin::ClangCompileCommands({&hello_world}).Generate(); + + return 0; +} + +static void clean_cb() { + env::log_info(EXE, fmt::format("Cleaning {}", Project::GetBuildDir())); + fs::remove_all(Project::GetBuildDir()); +} + +static void hello_world_build_cb(BaseTarget &target) { + target.AddSource("main.cpp", "src"); + + target.Build(); +} diff --git a/example/hybrid/single/build.toml b/example/hybrid/single/build.toml new file mode 100644 index 00000000..d849a6ef --- /dev/null +++ b/example/hybrid/single/build.toml @@ -0,0 +1,12 @@ +# Root +root_dir = "" +build_dir = "_build" +loglevel = "trace" + +# Project +clean = true + +# Toolchain +[toolchain.gcc] +build = true +test = true diff --git a/example/hybrid/single/compile.toml b/example/hybrid/single/compile.toml new file mode 100644 index 00000000..4c3f17b8 --- /dev/null +++ b/example/hybrid/single/compile.toml @@ -0,0 +1,17 @@ +# Settings +root_dir = "" +build_dir = "_build_internal" +loglevel = "info" +clean = true + +# BuildExe run mode +mode = "script" + +# Target information +name = "single" +type = "executable" +relative_to_root = "" +srcs = ["build.cpp"] + +[script] +configs = ["build.toml"] diff --git a/example/hybrid/single/immediate.toml b/example/hybrid/single/immediate.toml new file mode 100644 index 00000000..da544ed7 --- /dev/null +++ b/example/hybrid/single/immediate.toml @@ -0,0 +1,14 @@ +# Settings +root_dir = "" +build_dir = "_build" +loglevel = "trace" +clean = false + +# BuildExe run mode +mode = "immediate" + +# Target information +name = "hello_world" +type = "executable" +relative_to_root = "src" +srcs = ["main.cpp"] diff --git a/example/hybrid/single/src/main.cpp b/example/hybrid/single/src/main.cpp new file mode 100644 index 00000000..147d50de --- /dev/null +++ b/example/hybrid/single/src/main.cpp @@ -0,0 +1,6 @@ +#include + +int main() { + std::cout << "Hello World" << std::endl; + return 0; +} diff --git a/example/hybrid/target_info/.gitignore b/example/hybrid/target_info/.gitignore new file mode 100644 index 00000000..cb283a9c --- /dev/null +++ b/example/hybrid/target_info/.gitignore @@ -0,0 +1,10 @@ +# Folder +generated +buildcc +_internal* + +# Files +*.exe +*.o +*.bin +*.dot diff --git a/example/hybrid/target_info/CMakeLists.txt b/example/hybrid/target_info/CMakeLists.txt new file mode 100644 index 00000000..f1707fa4 --- /dev/null +++ b/example/hybrid/target_info/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.10.0) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +project(hybrid_targetinfo_example) + +# Bootstrap your build file using CMake +add_executable(hybrid_targetinfo_example build.cpp) +target_link_libraries(hybrid_targetinfo_example PRIVATE buildcc) + +# TODO, Add this only if MINGW is used +# https://github.com/msys2/MINGW-packages/issues/2303 +# Similar issue when adding the Taskflow library +if (${MINGW}) + message(WARNING "-Wl,--allow-multiple-definition for MINGW") + target_link_options(hybrid_targetinfo_example PRIVATE -Wl,--allow-multiple-definition) +endif() + +# Run your build file +add_custom_target(run_hybrid_targetinfo_example_win + COMMAND hybrid_targetinfo_example --help-all + COMMAND hybrid_targetinfo_example --config ${CMAKE_CURRENT_SOURCE_DIR}/build_win.toml + # COMMAND dot -Tpng graph.dot -o graph.PNG + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS hybrid_targetinfo_example buildcc + VERBATIM USES_TERMINAL +) + +add_custom_target(run_hybrid_targetinfo_example_linux + COMMAND hybrid_targetinfo_example --help-all + COMMAND hybrid_targetinfo_example --config ${CMAKE_CURRENT_SOURCE_DIR}/build_linux.toml + # COMMAND dot -Tpng graph.dot -o graph.PNG + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS hybrid_targetinfo_example buildcc + VERBATIM USES_TERMINAL +) diff --git a/example/hybrid/target_info/build.cpp b/example/hybrid/target_info/build.cpp new file mode 100644 index 00000000..e2684d0f --- /dev/null +++ b/example/hybrid/target_info/build.cpp @@ -0,0 +1,98 @@ +#include "buildcc.h" + +using namespace buildcc; + +constexpr const char *const EXE = "build"; + +// Function Prototypes +static void clean_cb(); +static void genericadd_ho_cb(TargetInfo &genericadd_ho); +static void genericadd1_build_cb(BaseTarget &genericadd, + const TargetInfo &genericadd_ho); +static void genericadd2_build_cb(BaseTarget &genericadd, + const TargetInfo &genericadd_ho); + +int main(int argc, char **argv) { + // Get arguments + ArgToolchain arg_gcc; + ArgToolchain arg_msvc; + Args::Init() + .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc) + .AddToolchain("msvc", "Generic msvc toolchain", arg_msvc) + .Parse(argc, argv); + + // Initialize your environment + Reg::Init(); + + // Pre-build steps + Reg::Call(Args::Clean()).Func(clean_cb); + + // Build steps + // Explicit toolchain - target pairs + Toolchain_gcc gcc; + TargetInfo gcc_genericadd_ho(gcc, "files"); + ExecutableTarget_gcc g_genericadd1("generic_add_1", gcc, "files"); + ExecutableTarget_gcc g_genericadd2("generic_add_2", gcc, "files"); + Reg::Toolchain(arg_gcc.state) + .Func([&]() { gcc.Verify(); }) + .Func(genericadd_ho_cb, gcc_genericadd_ho) + .Build(genericadd1_build_cb, g_genericadd1, gcc_genericadd_ho) + .Build(genericadd2_build_cb, g_genericadd2, gcc_genericadd_ho) + .Test("{executable}", g_genericadd1) + .Test("{executable}", g_genericadd2); + + Toolchain_msvc msvc; + TargetInfo msvc_genericadd_ho(msvc, "files"); + ExecutableTarget_msvc m_genericadd1("generic_add_1", msvc, "files"); + ExecutableTarget_msvc m_genericadd2("generic_add_2", msvc, "files"); + Reg::Toolchain(arg_msvc.state) + .Func([&]() { msvc.Verify(); }) + .Func(genericadd_ho_cb, msvc_genericadd_ho) + .Build(genericadd1_build_cb, m_genericadd1, msvc_genericadd_ho) + .Build(genericadd2_build_cb, m_genericadd2, msvc_genericadd_ho) + .Test("{executable}", m_genericadd1) + .Test("{executable}", m_genericadd2); + + // Run + Reg::Run(); + + // Post Build steps + // - Clang Compile Commands + plugin::ClangCompileCommands({&g_genericadd1, &m_genericadd1}).Generate(); + + // - Plugin Graph + std::string output = Reg::GetTaskflow().dump(); + const bool saved = env::save_file("graph.dot", output, false); + env::assert_fatal(saved, "Could not save graph.dot file"); + + return 0; +} + +static void clean_cb() { + env::log_info(EXE, fmt::format("Cleaning {}", Project::GetBuildDir())); + fs::remove_all(Project::GetBuildDir()); +} + +static void genericadd_ho_cb(TargetInfo &genericadd_ho) { + genericadd_ho.AddIncludeDir("include", true); +} + +static void genericadd1_build_cb(BaseTarget &genericadd, + const TargetInfo &genericadd_ho) { + genericadd.AddSource("src/main1.cpp"); + genericadd.Copy(genericadd_ho, { + SyncOption::IncludeDirs, + SyncOption::HeaderFiles, + }); + genericadd.Build(); +} + +static void genericadd2_build_cb(BaseTarget &genericadd, + const TargetInfo &genericadd_ho) { + genericadd.AddSource("src/main2.cpp"); + genericadd.Copy(genericadd_ho, { + SyncOption::IncludeDirs, + SyncOption::HeaderFiles, + }); + genericadd.Build(); +} diff --git a/example/hybrid/target_info/build_linux.toml b/example/hybrid/target_info/build_linux.toml new file mode 100644 index 00000000..25353efc --- /dev/null +++ b/example/hybrid/target_info/build_linux.toml @@ -0,0 +1,16 @@ +# Root +root_dir = "" +build_dir = "_internal_linux" +loglevel = "trace" + +# Project +clean = true + +# Toolchain +[toolchain.gcc] +build = true +test = true + +[toolchain.msvc] +build = false +test = false diff --git a/example/hybrid/target_info/build_win.toml b/example/hybrid/target_info/build_win.toml new file mode 100644 index 00000000..1a49112d --- /dev/null +++ b/example/hybrid/target_info/build_win.toml @@ -0,0 +1,16 @@ +# Root +root_dir = "" +build_dir = "_internal_win" +loglevel = "trace" + +# Project +clean = true + +# Toolchain +[toolchain.gcc] +build = true +test = true + +[toolchain.msvc] +build = true +test = true diff --git a/example/hybrid/target_info/files/include/generic_add.h b/example/hybrid/target_info/files/include/generic_add.h new file mode 100644 index 00000000..48e00209 --- /dev/null +++ b/example/hybrid/target_info/files/include/generic_add.h @@ -0,0 +1,3 @@ +#pragma once + +template T generic_add(T a, T b) { return a + b; } diff --git a/example/hybrid/target_info/files/include/generic_vector2.h b/example/hybrid/target_info/files/include/generic_vector2.h new file mode 100644 index 00000000..13ebf926 --- /dev/null +++ b/example/hybrid/target_info/files/include/generic_vector2.h @@ -0,0 +1,15 @@ +#pragma once + +template struct Vector2 { + Vector2() : x(0), y(0) {} + Vector2(T xi, T yi) : x(xi), y(yi) {} + + void Print() { std::cout << "X: " << x << " Y: " << y << std::endl; } + + Vector2 operator+(const Vector2 &other) { + return Vector2(x + other.x, y + other.y); + }; + + T x; + T y; +}; diff --git a/example/hybrid/target_info/files/src/main1.cpp b/example/hybrid/target_info/files/src/main1.cpp new file mode 100644 index 00000000..81ac4475 --- /dev/null +++ b/example/hybrid/target_info/files/src/main1.cpp @@ -0,0 +1,15 @@ +#include + +#include "generic_add.h" + +int main() { + std::cout << "Hello World from test" << std::endl; + + int int_add = generic_add(1, 2); + std::cout << "INT ADD: " << int_add << std::endl; + + float float_add = generic_add(1.1, 4.5); + std::cout << "FLOAT ADD: " << float_add << std::endl; + + return 0; +} diff --git a/example/hybrid/target_info/files/src/main2.cpp b/example/hybrid/target_info/files/src/main2.cpp new file mode 100644 index 00000000..921d4a68 --- /dev/null +++ b/example/hybrid/target_info/files/src/main2.cpp @@ -0,0 +1,20 @@ +#include + +#include "generic_add.h" +#include "generic_vector2.h" + +int main() { + std::cout << "Hello World from test" << std::endl; + + Vector2 vi1(2, 4); + Vector2 vi2(3, 6); + Vector2 vi3 = generic_add(vi1, vi2); + vi3.Print(); + + Vector2 vf1(1.1, 2.2); + Vector2 vf2(2.2, 3.3); + Vector2 vf3 = generic_add(vf1, vf2); + vf3.Print(); + + return 0; +} diff --git a/example/mingw/DynamicLib/.gitignore b/example/mingw/DynamicLib/.gitignore new file mode 100644 index 00000000..99b60608 --- /dev/null +++ b/example/mingw/DynamicLib/.gitignore @@ -0,0 +1,8 @@ +# Folder +generated +buildcc + +# Files +*.exe +*.o +*.bin diff --git a/example/mingw/DynamicLib/CMakeLists.txt b/example/mingw/DynamicLib/CMakeLists.txt new file mode 100644 index 00000000..c7987830 --- /dev/null +++ b/example/mingw/DynamicLib/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.10.0) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +project(simple) + +# buildcc should be added as a Shared Library, or Object Library +# Link time for Static libraries is huge +set(BUILDCC_TESTING OFF CACHE BOOL "BuildCC Testing") +add_subdirectory(../../../ BuildCC) + +# build executable +add_executable(build build.cpp) +target_include_directories(build PRIVATE ${CMAKE_BINARY_DIR}/generated) +target_link_libraries(build PRIVATE buildcc) + +# Add your constants file for the environment +configure_file(constants.h.in ${CMAKE_BINARY_DIR}/generated/constants.h @ONLY) diff --git a/example/mingw/DynamicLib/build.cpp b/example/mingw/DynamicLib/build.cpp new file mode 100644 index 00000000..1de9c662 --- /dev/null +++ b/example/mingw/DynamicLib/build.cpp @@ -0,0 +1,63 @@ +#include +#include + +#include "buildcc.h" +#include "constants.h" + +using namespace buildcc; + +// - Windows MinGW 10.2.0 +int main(void) { + // Environment is meant to define + // 1. Project Root path i.e all files and targets will be added relative to + // this path + // 2. Intermediate build folder i.e all intermediate generated files should go + // here + Project::Init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); + env::set_log_level(env::LogLevel::Trace); + + Toolchain_mingw mingw; + + DynamicTarget_mingw dynamictarget("librandom", mingw, "files"); + dynamictarget.AddSource("src/random.cpp"); + dynamictarget.AddHeader("include/random.h"); + dynamictarget.AddIncludeDir("include"); + dynamictarget.Build(); + + ExecutableTarget_mingw target("dynamictest", mingw, "files"); + target.AddSource("main.cpp", "src"); + target.AddIncludeDir("include"); + + // * Method 1 + // NOTE, Use buildcc built targets + target.AddLibDep(dynamictarget); + + // * Method 2, External lib + // target.AddLibDirAbsolute(dynamictarget.GetTargetBuildDir()); + // target.AddLibDep("-lrandom"); + + target.Build(); + + tf::Executor executor; + tf::Taskflow taskflow; + auto dynamictargetTask = taskflow.composed_of(dynamictarget.GetTaskflow()); + auto targetTask = taskflow.composed_of(target.GetTaskflow()); + + targetTask.succeed(dynamictargetTask); + + executor.run(taskflow); + executor.wait_for_all(); + + // Post Build step + if (target.IsBuilt()) { + fs::path copy_to_path = + target.GetTargetBuildDir() / dynamictarget.GetTargetPath().filename(); + fs::remove_all(copy_to_path); + fs::copy(dynamictarget.GetTargetPath(), copy_to_path); + } + + // Dump .dot output + taskflow.dump(std::cout); + + return 0; +} diff --git a/example/mingw/DynamicLib/constants.h.in b/example/mingw/DynamicLib/constants.h.in new file mode 100644 index 00000000..b3469908 --- /dev/null +++ b/example/mingw/DynamicLib/constants.h.in @@ -0,0 +1,5 @@ +#pragma once + +// clang-format off +inline constexpr const char * const BUILD_ROOT = "@CMAKE_SOURCE_DIR@"; +inline constexpr const char * const BUILD_INTERMEDIATE_DIR = "@CMAKE_SOURCE_DIR@/buildcc"; diff --git a/example/mingw/DynamicLib/files/include/random.h b/example/mingw/DynamicLib/files/include/random.h new file mode 100644 index 00000000..6aa3357d --- /dev/null +++ b/example/mingw/DynamicLib/files/include/random.h @@ -0,0 +1,3 @@ +#pragma once + +void random_print(); diff --git a/buildcc/lib/target/test/path/path_main.cpp b/example/mingw/DynamicLib/files/src/main.cpp similarity index 72% rename from buildcc/lib/target/test/path/path_main.cpp rename to example/mingw/DynamicLib/files/src/main.cpp index 18a69789..45b791ce 100644 --- a/buildcc/lib/target/test/path/path_main.cpp +++ b/example/mingw/DynamicLib/files/src/main.cpp @@ -1,6 +1,9 @@ #include +#include "random.h" + int main() { std::cout << "Hello World from test" << std::endl; + random_print(); return 0; } diff --git a/example/mingw/DynamicLib/files/src/random.cpp b/example/mingw/DynamicLib/files/src/random.cpp new file mode 100644 index 00000000..e0b8e775 --- /dev/null +++ b/example/mingw/DynamicLib/files/src/random.cpp @@ -0,0 +1,5 @@ +#include "random.h" + +#include + +void random_print() { std::cout << __FUNCTION__ << std::endl; } diff --git a/example/mingw/Executable/.gitignore b/example/mingw/Executable/.gitignore new file mode 100644 index 00000000..99b60608 --- /dev/null +++ b/example/mingw/Executable/.gitignore @@ -0,0 +1,8 @@ +# Folder +generated +buildcc + +# Files +*.exe +*.o +*.bin diff --git a/example/mingw/Executable/CMakeLists.txt b/example/mingw/Executable/CMakeLists.txt new file mode 100644 index 00000000..c7987830 --- /dev/null +++ b/example/mingw/Executable/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.10.0) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +project(simple) + +# buildcc should be added as a Shared Library, or Object Library +# Link time for Static libraries is huge +set(BUILDCC_TESTING OFF CACHE BOOL "BuildCC Testing") +add_subdirectory(../../../ BuildCC) + +# build executable +add_executable(build build.cpp) +target_include_directories(build PRIVATE ${CMAKE_BINARY_DIR}/generated) +target_link_libraries(build PRIVATE buildcc) + +# Add your constants file for the environment +configure_file(constants.h.in ${CMAKE_BINARY_DIR}/generated/constants.h @ONLY) diff --git a/example/mingw/Executable/build.cpp b/example/mingw/Executable/build.cpp new file mode 100644 index 00000000..41550319 --- /dev/null +++ b/example/mingw/Executable/build.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include "buildcc.h" +#include "constants.h" + +using namespace buildcc; + +int main(void) { + // Environment is meant to define + // 1. Project Root path i.e all files and targets will be added relative to + // this path + // 2. Intermediate build folder i.e all intermediate generated files should go + // here + Project::Init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); + env::set_log_level(env::LogLevel::Trace); + + Toolchain_mingw mingw; + + // CppTarget + ExecutableTarget_mingw cpptarget("CppFlags", mingw, "files"); + cpptarget.AddSource("main.cpp", "src"); + cpptarget.AddSource("src/random.cpp"); + cpptarget.AddHeader("include/random.h"); + cpptarget.AddIncludeDir("include"); + cpptarget.AddPreprocessorFlag("-DRANDOM=1"); + cpptarget.AddCppCompileFlag("-Wall"); + cpptarget.AddCppCompileFlag("-Werror"); + cpptarget.AddLinkFlag("-lm"); + cpptarget.Build(); + + // CTarget + ExecutableTarget_mingw ctarget("CFlags", mingw, "files"); + ctarget.AddSource("main.c", "src"); + ctarget.AddPreprocessorFlag("-DRANDOM=1"); + ctarget.AddCCompileFlag("-Wall"); + ctarget.AddCCompileFlag("-Werror"); + ctarget.AddLinkFlag("-lm"); + ctarget.Build(); + + // Run + tf::Executor executor; + tf::Taskflow taskflow; + taskflow.composed_of(cpptarget.GetTaskflow()); + taskflow.composed_of(ctarget.GetTaskflow()); + executor.run(taskflow); + executor.wait_for_all(); + + taskflow.dump(std::cout); + + return 0; +} diff --git a/example/mingw/Executable/constants.h.in b/example/mingw/Executable/constants.h.in new file mode 100644 index 00000000..b3469908 --- /dev/null +++ b/example/mingw/Executable/constants.h.in @@ -0,0 +1,5 @@ +#pragma once + +// clang-format off +inline constexpr const char * const BUILD_ROOT = "@CMAKE_SOURCE_DIR@"; +inline constexpr const char * const BUILD_INTERMEDIATE_DIR = "@CMAKE_SOURCE_DIR@/buildcc"; diff --git a/example/mingw/Executable/files/include/random.h b/example/mingw/Executable/files/include/random.h new file mode 100644 index 00000000..6aa3357d --- /dev/null +++ b/example/mingw/Executable/files/include/random.h @@ -0,0 +1,3 @@ +#pragma once + +void random_print(); diff --git a/example/mingw/Executable/files/src/main.c b/example/mingw/Executable/files/src/main.c new file mode 100644 index 00000000..090e9efe --- /dev/null +++ b/example/mingw/Executable/files/src/main.c @@ -0,0 +1,23 @@ +#include +#include + +int main() { + printf("Hello World\r\n"); + + // -Wall -Werror throw error when unused variable + // int i = 0; + +#if RANDOM == 1 + printf("Random == 1\r\n"); +#endif + + float f1 = 10.2; + float f2 = 1.0; + printf("Input a floating point number\r\n"); + scanf("%f", &f2); + + float value = pow(f1, f2); + printf("Computed pow: %f\r\n", value); + + return 0; +} diff --git a/example/mingw/Executable/files/src/main.cpp b/example/mingw/Executable/files/src/main.cpp new file mode 100644 index 00000000..01820c25 --- /dev/null +++ b/example/mingw/Executable/files/src/main.cpp @@ -0,0 +1,25 @@ +#include +#include + +#include "random.h" + +int main() { + std::cout << "Hello World from test" << std::endl; + + // -Wall -Werror flags throw error when unused variable + // int i = 0; + +#if RANDOM == 1 + random_print(); +#endif + + float f1 = 10.2; + float f2 = 1.0; + std::cout << "input a floating point number" << std::endl; + std::cin >> f2; + + float value = pow(f1, f2); + std::cout << value << std::endl; + + return 0; +} diff --git a/example/mingw/Executable/files/src/random.cpp b/example/mingw/Executable/files/src/random.cpp new file mode 100644 index 00000000..e0b8e775 --- /dev/null +++ b/example/mingw/Executable/files/src/random.cpp @@ -0,0 +1,5 @@ +#include "random.h" + +#include + +void random_print() { std::cout << __FUNCTION__ << std::endl; } diff --git a/example/mingw/Pch/.gitignore b/example/mingw/Pch/.gitignore new file mode 100644 index 00000000..99b60608 --- /dev/null +++ b/example/mingw/Pch/.gitignore @@ -0,0 +1,8 @@ +# Folder +generated +buildcc + +# Files +*.exe +*.o +*.bin diff --git a/example/mingw/Pch/CMakeLists.txt b/example/mingw/Pch/CMakeLists.txt new file mode 100644 index 00000000..c7987830 --- /dev/null +++ b/example/mingw/Pch/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.10.0) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +project(simple) + +# buildcc should be added as a Shared Library, or Object Library +# Link time for Static libraries is huge +set(BUILDCC_TESTING OFF CACHE BOOL "BuildCC Testing") +add_subdirectory(../../../ BuildCC) + +# build executable +add_executable(build build.cpp) +target_include_directories(build PRIVATE ${CMAKE_BINARY_DIR}/generated) +target_link_libraries(build PRIVATE buildcc) + +# Add your constants file for the environment +configure_file(constants.h.in ${CMAKE_BINARY_DIR}/generated/constants.h @ONLY) diff --git a/example/mingw/Pch/build.cpp b/example/mingw/Pch/build.cpp new file mode 100644 index 00000000..c4747467 --- /dev/null +++ b/example/mingw/Pch/build.cpp @@ -0,0 +1,70 @@ +#include "buildcc.h" +#include "constants.h" + +using namespace buildcc; + +constexpr const char *const EXE = "build"; + +// Function Prototypes +static void cppflags_build_cb(BaseTarget &cppflags); +static void cflags_build_cb(BaseTarget &cflags); + +int main() { + // Environment is meant to define + // 1. Project Root path i.e all files and targets will be added relative to + // this path + // 2. Intermediate build folder i.e all intermediate generated files should go + // here + Project::Init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); + env::set_log_level(env::LogLevel::Trace); + + Toolchain_mingw mingw; + + ExecutableTarget_mingw g_cppflags("cppflags", mingw, "files"); + cppflags_build_cb(g_cppflags); + + ExecutableTarget_mingw g_cflags("cflags", mingw, "files"); + cflags_build_cb(g_cflags); + + tf::Executor executor; + tf::Taskflow taskflow; + + taskflow.composed_of(g_cppflags.GetTaskflow()); + taskflow.composed_of(g_cflags.GetTaskflow()); + executor.run(taskflow); + executor.wait_for_all(); + + return 0; +} + +static void cppflags_build_cb(BaseTarget &cppflags) { + cppflags.AddSource("main.cpp", "src"); + cppflags.AddSource("random.cpp", "src"); + cppflags.AddIncludeDir("include", true); + + cppflags.AddPch("pch/pch_cpp.h"); + cppflags.AddPch("pch/pch_c.h"); + cppflags.AddIncludeDir("pch", true); + + cppflags.AddPreprocessorFlag("-DRANDOM=1"); + cppflags.AddCppCompileFlag("-Wall"); + cppflags.AddCppCompileFlag("-Werror"); + cppflags.AddLinkFlag("-lm"); + + cppflags.Build(); +} + +static void cflags_build_cb(BaseTarget &cflags) { + cflags.AddSource("main.c", "src"); + + cflags.AddPch("pch/pch_c.h"); + cflags.AddIncludeDir("pch", false); + cflags.AddHeader("pch/pch_c.h"); + + cflags.AddPreprocessorFlag("-DRANDOM=1"); + cflags.AddCCompileFlag("-Wall"); + cflags.AddCCompileFlag("-Werror"); + cflags.AddLinkFlag("-lm"); + + cflags.Build(); +} diff --git a/example/mingw/Pch/constants.h.in b/example/mingw/Pch/constants.h.in new file mode 100644 index 00000000..b3469908 --- /dev/null +++ b/example/mingw/Pch/constants.h.in @@ -0,0 +1,5 @@ +#pragma once + +// clang-format off +inline constexpr const char * const BUILD_ROOT = "@CMAKE_SOURCE_DIR@"; +inline constexpr const char * const BUILD_INTERMEDIATE_DIR = "@CMAKE_SOURCE_DIR@/buildcc"; diff --git a/example/mingw/Pch/files/include/random.h b/example/mingw/Pch/files/include/random.h new file mode 100644 index 00000000..6aa3357d --- /dev/null +++ b/example/mingw/Pch/files/include/random.h @@ -0,0 +1,3 @@ +#pragma once + +void random_print(); diff --git a/example/mingw/Pch/files/pch/pch_c.h b/example/mingw/Pch/files/pch/pch_c.h new file mode 100644 index 00000000..56d69117 --- /dev/null +++ b/example/mingw/Pch/files/pch/pch_c.h @@ -0,0 +1,7 @@ +#ifndef PCH_C_H_ +#define PCH_C_H_ + +#include +#include + +#endif diff --git a/example/mingw/Pch/files/pch/pch_cpp.h b/example/mingw/Pch/files/pch/pch_cpp.h new file mode 100644 index 00000000..54896970 --- /dev/null +++ b/example/mingw/Pch/files/pch/pch_cpp.h @@ -0,0 +1,9 @@ +#ifndef PCH_CPP_H_ +#define PCH_CPP_H_ + +#include +#include + +#include "random.h" + +#endif diff --git a/example/mingw/Pch/files/src/main.c b/example/mingw/Pch/files/src/main.c new file mode 100644 index 00000000..efbfc706 --- /dev/null +++ b/example/mingw/Pch/files/src/main.c @@ -0,0 +1,20 @@ +#include "pch_c.h" + +int main() { + printf("Hello World\r\n"); + + // -Wall -Werror throw error when unused variable + // int i = 0; + +#if RANDOM == 1 + printf("Random == 1\r\n"); +#endif + + float f1 = 10.2; + float f2 = 2.3; + + float value = pow(f1, f2); + printf("Computed pow: %f\r\n", value); + + return 0; +} diff --git a/example/mingw/Pch/files/src/main.cpp b/example/mingw/Pch/files/src/main.cpp new file mode 100644 index 00000000..9598d166 --- /dev/null +++ b/example/mingw/Pch/files/src/main.cpp @@ -0,0 +1,20 @@ +#include "pch_cpp.h" + +int main() { + std::cout << "Hello World from test" << std::endl; + + // -Wall -Werror flags throw error when unused variable + // int i = 0; + +#if RANDOM == 1 + random_print(); +#endif + + float f1 = 10.2; + float f2 = 2.3; + + float value = pow(f1, f2); + std::cout << value << std::endl; + + return 0; +} diff --git a/example/mingw/Pch/files/src/random.cpp b/example/mingw/Pch/files/src/random.cpp new file mode 100644 index 00000000..14559d96 --- /dev/null +++ b/example/mingw/Pch/files/src/random.cpp @@ -0,0 +1,3 @@ +#include "pch_cpp.h" + +void random_print() { std::cout << __FUNCTION__ << std::endl; } diff --git a/example/mingw/StaticLib/.gitignore b/example/mingw/StaticLib/.gitignore new file mode 100644 index 00000000..99b60608 --- /dev/null +++ b/example/mingw/StaticLib/.gitignore @@ -0,0 +1,8 @@ +# Folder +generated +buildcc + +# Files +*.exe +*.o +*.bin diff --git a/example/mingw/StaticLib/CMakeLists.txt b/example/mingw/StaticLib/CMakeLists.txt new file mode 100644 index 00000000..c7987830 --- /dev/null +++ b/example/mingw/StaticLib/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.10.0) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +project(simple) + +# buildcc should be added as a Shared Library, or Object Library +# Link time for Static libraries is huge +set(BUILDCC_TESTING OFF CACHE BOOL "BuildCC Testing") +add_subdirectory(../../../ BuildCC) + +# build executable +add_executable(build build.cpp) +target_include_directories(build PRIVATE ${CMAKE_BINARY_DIR}/generated) +target_link_libraries(build PRIVATE buildcc) + +# Add your constants file for the environment +configure_file(constants.h.in ${CMAKE_BINARY_DIR}/generated/constants.h @ONLY) diff --git a/example/mingw/StaticLib/build.cpp b/example/mingw/StaticLib/build.cpp new file mode 100644 index 00000000..628d5081 --- /dev/null +++ b/example/mingw/StaticLib/build.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include "buildcc.h" +#include "constants.h" + +using namespace buildcc; + +int main(void) { + // Environment is meant to define + // 1. Project Root path i.e all files and targets will be added relative to + // this path + // 2. Intermediate build folder i.e all intermediate generated files should go + // here + Project::Init(BUILD_ROOT, BUILD_INTERMEDIATE_DIR); + env::set_log_level(env::LogLevel::Trace); + + Toolchain_mingw mingw; + + StaticTarget_mingw statictarget("librandom", mingw, "files"); + statictarget.AddSource("src/random.cpp"); + statictarget.AddHeader("include/random.h"); + statictarget.AddIncludeDir("include"); + statictarget.Build(); + + ExecutableTarget_mingw exetarget("statictest", mingw, "files"); + exetarget.AddSource("main.cpp", "src"); + exetarget.AddIncludeDir("include"); + + // * Method 1 + // NOTE, Use buildcc built targets + exetarget.AddLibDep(statictarget); + + // * Method 2 + // NOTE, This should be an absolute path since we could also be referencing + // external libs that are not relative to this project + // exetarget.AddLibDirAbsolute(statictarget.GetTargetPath().parent_path()); + // exetarget.AddLibDep("-lrandom"); + + exetarget.Build(); + + // Run + tf::Executor executor; + tf::Taskflow taskflow; + + tf::Task statictargetTask = taskflow.composed_of(statictarget.GetTaskflow()); + tf::Task exetargetTask = taskflow.composed_of(exetarget.GetTaskflow()); + + // Set dependency + exetargetTask.succeed(statictargetTask); + + // Run + executor.run(taskflow); + executor.wait_for_all(); + + // Dump .dot out + taskflow.dump(std::cout); + + return 0; +} diff --git a/example/mingw/StaticLib/constants.h.in b/example/mingw/StaticLib/constants.h.in new file mode 100644 index 00000000..b3469908 --- /dev/null +++ b/example/mingw/StaticLib/constants.h.in @@ -0,0 +1,5 @@ +#pragma once + +// clang-format off +inline constexpr const char * const BUILD_ROOT = "@CMAKE_SOURCE_DIR@"; +inline constexpr const char * const BUILD_INTERMEDIATE_DIR = "@CMAKE_SOURCE_DIR@/buildcc"; diff --git a/example/mingw/StaticLib/files/include/random.h b/example/mingw/StaticLib/files/include/random.h new file mode 100644 index 00000000..6aa3357d --- /dev/null +++ b/example/mingw/StaticLib/files/include/random.h @@ -0,0 +1,3 @@ +#pragma once + +void random_print(); diff --git a/example/mingw/StaticLib/files/src/main.cpp b/example/mingw/StaticLib/files/src/main.cpp new file mode 100644 index 00000000..45b791ce --- /dev/null +++ b/example/mingw/StaticLib/files/src/main.cpp @@ -0,0 +1,9 @@ +#include + +#include "random.h" + +int main() { + std::cout << "Hello World from test" << std::endl; + random_print(); + return 0; +} diff --git a/example/mingw/StaticLib/files/src/random.cpp b/example/mingw/StaticLib/files/src/random.cpp new file mode 100644 index 00000000..e0b8e775 --- /dev/null +++ b/example/mingw/StaticLib/files/src/random.cpp @@ -0,0 +1,5 @@ +#include "random.h" + +#include + +void random_print() { std::cout << __FUNCTION__ << std::endl; } diff --git a/example/msvc/DynamicLib/build.cpp b/example/msvc/DynamicLib/build.cpp index c7fc1816..7c20a2f9 100644 --- a/example/msvc/DynamicLib/build.cpp +++ b/example/msvc/DynamicLib/build.cpp @@ -4,8 +4,6 @@ #include "buildcc.h" #include "constants.h" -#include "clang_compile_commands.h" - using namespace buildcc; int main(void) { @@ -14,52 +12,46 @@ int main(void) { // this path // 2. Intermediate build folder i.e all intermediate generated files should go // here - env::init(BUILD_SCRIPT_SOURCE, BUILD_SCRIPT_FOLDER); + Project::Init(BUILD_SCRIPT_SOURCE, BUILD_SCRIPT_FOLDER); env::set_log_level(env::LogLevel::Debug); - // base::Toolchain msvc("msvc", "cl", "cl", "cl", "lib", "link"); Toolchain_msvc msvc; - DynamicTarget_msvc dynamictarget("librandom.lib", msvc, ""); + DynamicTarget_msvc dynamictarget("librandom", msvc, ""); dynamictarget.AddSource("src/random.cpp"); dynamictarget.AddIncludeDir("include", true); - dynamictarget.AddCppCompileFlag("/EHsc"); - dynamictarget.AddCppCompileFlag("/nologo"); - dynamictarget.AddLinkFlag("/nologo"); dynamictarget.Build(); - ExecutableTarget_msvc target_msvc("Simple.exe", msvc, ""); - target_msvc.AddSource("src/main.cpp"); - target_msvc.AddIncludeDir("include", true); + ExecutableTarget_msvc exetarget("Simple", msvc, ""); + exetarget.AddSource("src/main.cpp"); + exetarget.AddIncludeDir("include", true); // Method 1 - // target_msvc.AddLibDep(dynamictarget); + exetarget.AddLibDep(dynamictarget); // Method 2 - target_msvc.AddLibDep("librandom.lib"); - target_msvc.AddLibDir(dynamictarget.GetTargetPath().parent_path()); - - target_msvc.AddCppCompileFlag("/EHsc"); - target_msvc.AddCppCompileFlag("/nologo"); - target_msvc.AddLinkFlag("/nologo"); - target_msvc.Build(); + // exetarget.AddLibDep("librandom.lib"); + // exetarget.AddLibDir(dynamictarget.GetTargetPath().parent_path()); + exetarget.Build(); + // Manually setup your dependencies tf::Executor executor; tf::Taskflow taskflow; auto dynamictargetTask = taskflow.composed_of(dynamictarget.GetTaskflow()); - auto target_msvcTask = taskflow.composed_of(target_msvc.GetTaskflow()); - target_msvcTask.succeed(dynamictargetTask); + auto exetargetTask = taskflow.composed_of(exetarget.GetTaskflow()); + exetargetTask.succeed(dynamictargetTask); executor.run(taskflow); executor.wait_for_all(); - if (target_msvc.FirstBuild() || target_msvc.Rebuild()) { - fs::copy(dynamictarget.GetTargetPath().string() + ".dll", - target_msvc.GetTargetPath().parent_path() / "librandom.lib.dll"); + if (exetarget.IsBuilt()) { + fs::copy(dynamictarget.GetDllPath(), + exetarget.GetTargetPath().parent_path() / + dynamictarget.GetDllPath().filename()); } - plugin::ClangCompileCommands({&dynamictarget, &target_msvc}).Generate(); + plugin::ClangCompileCommands({&dynamictarget, &exetarget}).Generate(); taskflow.dump(std::cout); diff --git a/example/msvc/Executable/build.cpp b/example/msvc/Executable/build.cpp index 033a8ae7..8ea7d4c9 100644 --- a/example/msvc/Executable/build.cpp +++ b/example/msvc/Executable/build.cpp @@ -4,8 +4,6 @@ #include "buildcc.h" #include "constants.h" -#include "clang_compile_commands.h" - using namespace buildcc; int main(void) { @@ -14,28 +12,23 @@ int main(void) { // this path // 2. Intermediate build folder i.e all intermediate generated files should go // here - env::init(BUILD_SCRIPT_SOURCE, BUILD_SCRIPT_FOLDER); + Project::Init(BUILD_SCRIPT_SOURCE, BUILD_SCRIPT_FOLDER); env::set_log_level(env::LogLevel::Trace); - // base::Toolchain msvc("msvc", "cl", "cl", "cl", "lib", "link"); Toolchain_msvc msvc; - ExecutableTarget_msvc target_msvc("Simple.exe", msvc, ""); - target_msvc.GlobSources("src"); - target_msvc.AddIncludeDir("include", true); - - target_msvc.AddCppCompileFlag("/EHsc"); - target_msvc.AddCppCompileFlag("/nologo"); - target_msvc.AddLinkFlag("/nologo"); - target_msvc.Build(); + ExecutableTarget_msvc exetarget("Simple", msvc, ""); + exetarget.GlobSources("src"); + exetarget.AddIncludeDir("include", true); + exetarget.Build(); - plugin::ClangCompileCommands({&target_msvc}).Generate(); + plugin::ClangCompileCommands({&exetarget}).Generate(); tf::Executor executor; - executor.run(target_msvc.GetTaskflow()); + executor.run(exetarget.GetTaskflow()); executor.wait_for_all(); - target_msvc.GetTaskflow().dump(std::cout); + exetarget.GetTaskflow().dump(std::cout); return 0; } diff --git a/example/msvc/StaticLib/build.cpp b/example/msvc/StaticLib/build.cpp index 5aa8cb20..5410df18 100644 --- a/example/msvc/StaticLib/build.cpp +++ b/example/msvc/StaticLib/build.cpp @@ -4,8 +4,6 @@ #include "buildcc.h" #include "constants.h" -#include "clang_compile_commands.h" - using namespace buildcc; int main(void) { @@ -14,44 +12,36 @@ int main(void) { // this path // 2. Intermediate build folder i.e all intermediate generated files should go // here - env::init(BUILD_SCRIPT_SOURCE, BUILD_SCRIPT_FOLDER); + Project::Init(BUILD_SCRIPT_SOURCE, BUILD_SCRIPT_FOLDER); env::set_log_level(env::LogLevel::Trace); - // base::Toolchain msvc("msvc", "cl", "cl", "cl", "lib", "link"); Toolchain_msvc msvc; - StaticTarget_msvc statictarget_msvc("librandom.lib", msvc, ""); - statictarget_msvc.AddSource("src/random.cpp"); - statictarget_msvc.AddIncludeDir("include", true); - statictarget_msvc.AddCppCompileFlag("/EHsc"); - statictarget_msvc.AddCppCompileFlag("/nologo"); - statictarget_msvc.AddLinkFlag("/nologo"); - statictarget_msvc.Build(); + StaticTarget_msvc statictarget("librandom", msvc, ""); + statictarget.AddSource("src/random.cpp"); + statictarget.AddIncludeDir("include", true); + statictarget.Build(); - ExecutableTarget_msvc target_msvc("Simple.exe", msvc, ""); - target_msvc.AddSource("src/main.cpp"); - target_msvc.AddIncludeDir("include", true); + ExecutableTarget_msvc exetarget("Simple", msvc, ""); + exetarget.AddSource("src/main.cpp"); + exetarget.AddIncludeDir("include", true); // Method 1 - // target_msvc.AddLibDep(statictarget_msvc); + exetarget.AddLibDep(statictarget); // Method 2 - target_msvc.AddLibDep("librandom.lib"); - target_msvc.AddLibDir(statictarget_msvc.GetTargetPath().parent_path()); - - target_msvc.AddCppCompileFlag("/EHsc"); - target_msvc.AddCppCompileFlag("/nologo"); - target_msvc.AddLinkFlag("/nologo"); - target_msvc.Build(); + // exetarget.AddLibDep("librandom.lib"); + // exetarget.AddLibDir(statictarget.GetTargetPath().parent_path()); + exetarget.Build(); - plugin::ClangCompileCommands({&target_msvc}).Generate(); + plugin::ClangCompileCommands({&exetarget}).Generate(); + // Manually setup your dependencies tf::Executor executor; tf::Taskflow taskflow; - auto statictarget_msvcTask = - taskflow.composed_of(statictarget_msvc.GetTaskflow()); - auto target_msvcTask = taskflow.composed_of(target_msvc.GetTaskflow()); - target_msvcTask.succeed(statictarget_msvcTask); + auto statictargetTask = taskflow.composed_of(statictarget.GetTaskflow()); + auto exetargetTask = taskflow.composed_of(exetarget.GetTaskflow()); + exetargetTask.succeed(statictargetTask); executor.run(taskflow); executor.wait_for_all(); diff --git a/flatbuffers b/flatbuffers deleted file mode 160000 index 6df40a24..00000000 --- a/flatbuffers +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6df40a2471737b27271bdd9b900ab5f3aec746c7 diff --git a/fmt b/fmt deleted file mode 160000 index 7bdf0628..00000000 --- a/fmt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7bdf0628b1276379886c7f6dda2cef2b3b374f0b diff --git a/spdlog b/spdlog deleted file mode 160000 index de0dbfa3..00000000 --- a/spdlog +++ /dev/null @@ -1 +0,0 @@ -Subproject commit de0dbfa3596a18cd70a4619b6a9766847a941276 diff --git a/third_party/CLI11 b/third_party/CLI11 new file mode 160000 index 00000000..ac29910a --- /dev/null +++ b/third_party/CLI11 @@ -0,0 +1 @@ +Subproject commit ac29910a69e9699c15cad9cb95cecce453433edb diff --git a/cpputest b/third_party/cpputest similarity index 100% rename from cpputest rename to third_party/cpputest diff --git a/third_party/fmt b/third_party/fmt new file mode 160000 index 00000000..d141cdbe --- /dev/null +++ b/third_party/fmt @@ -0,0 +1 @@ +Subproject commit d141cdbeb0fb422a3fb7173b285fd38e0d1772dc diff --git a/third_party/json b/third_party/json new file mode 160000 index 00000000..bc889afb --- /dev/null +++ b/third_party/json @@ -0,0 +1 @@ +Subproject commit bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d diff --git a/third_party/spdlog b/third_party/spdlog new file mode 160000 index 00000000..eb322062 --- /dev/null +++ b/third_party/spdlog @@ -0,0 +1 @@ +Subproject commit eb3220622e73a4889eee355ffa37972b3cac3df5 diff --git a/taskflow b/third_party/taskflow similarity index 100% rename from taskflow rename to third_party/taskflow diff --git a/tiny-process-library b/third_party/tiny-process-library similarity index 100% rename from tiny-process-library rename to third_party/tiny-process-library diff --git a/third_party/tl_optional b/third_party/tl_optional new file mode 160000 index 00000000..c28fcf74 --- /dev/null +++ b/third_party/tl_optional @@ -0,0 +1 @@ +Subproject commit c28fcf74d207fc667c4ed3dbae4c251ea551c8c1