diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e43968b..5a806b10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,8 +14,9 @@ option(BUILDCC_TESTING "Enable BuildCC Testing" OFF) option(BUILDCC_INSTALL "Enable Buildcc Installation" ON) option(BUILDCC_EXAMPLES "Enable Buildcc Examples" OFF) -option(BUILDCC_CLANGTIDY "Enable ClangTidy" ON) +option(BUILDCC_CLANGTIDY "Enable ClangTidy" OFF) option(BUILDCC_CPPCHECK "Enable CppCheck" OFF) +option(BUILDCC_DOCUMENTATION "Enable Documentation" OFF) # 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 @@ -52,6 +53,7 @@ else() endif() include(cmake/tool/clangtidy.cmake) include(cmake/tool/cppcheck.cmake) +include(cmake/tool/doxygen.cmake) # Libraries diff --git a/CMakePresets.json b/CMakePresets.json index ee19e27c..33256e2b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -19,6 +19,7 @@ "BUILDCC_CLANGTIDY": true, "BUILDCC_INSTALL": true, "BUILDCC_NO_DEPRECATED": false, + "BUILDCC_DOCUMENTATION": true, "CMAKE_BUILD_TYPE": "Debug", "CMAKE_C_COMPILER": "gcc", "CMAKE_CXX_COMPILER": "g++" @@ -37,6 +38,7 @@ "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++" @@ -55,6 +57,7 @@ "BUILDCC_CLANGTIDY": true, "BUILDCC_INSTALL": true, "BUILDCC_NO_DEPRECATED": false, + "BUILDCC_DOCUMENTATION": true, "CMAKE_BUILD_TYPE": "Debug" } } diff --git a/README.md b/README.md index 12d07327..59ad5fe5 100644 --- a/README.md +++ b/README.md @@ -4,47 +4,190 @@ Build C, C++ and ASM files in C++ # Aim +**_BuildCC_** aims to be an alternative to **Makefiles** while using the feature rich C++ language. + +# General Information + +- A `compile` + `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 +- 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. +- 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. + +**Taskflow dependency for hybrid/simple example** +![Taskflow dependency](example/hybrid/simple/graph.PNG) +See also [Software Architecture](#software-architecture) + +## Features + +- Complete flexibility for custom / brand new 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. + +## Software Architecture + +![Library dependency](doc/software_architecture/buildcc_core_dep.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. + +- 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 -## Usage +```bash +# 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 -## [Examples](example/README.md) +## Examples -Contains **proof of concept** and **real world** examples. +Contains **proof of concept** and **real world** [examples](example/README.md). # Developer +Developers interested in contributing to **_BuildCC_** + ## 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 -> TODO, Add more fields for developers +- 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) -# General +# FAQ -## Software Architecture +- [Why has _this_ third-party library been chosen?](doc/faq/why_this_lib.md) -## Features +## Design -## Community Plugin +- [Why do you track _include directories_ and _header files_?](doc/faq/include_dir_vs_header_files.md) -# FAQ +## Miscellaneous + +- [Why `-Wl,--allow-multiple-definition` for MINGW?](doc/faq/mingw_taskflow_linker_option.md) + +# TODO -# [TODO](TODO.md) +[List of features](TODO.md) to be implemented before buildcc can be considered production ready. -List of features to be implemented before buildcc can be considered production ready. +I would also like to request help from the Community for the following: +- Code reviews +- Design patterns +- Optimization strategies +- TODO discussions # License Dependencies _BuildCC_ is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text. _BuildCC_ aims to use open-source libraries containing permissive licenses. -> Users who would like to suggest an alternative library, raise an issue with the **license** and **advantages** clearly outlined. +> 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] - [Spdlog](https://github.com/gabime/spdlog) (Logging) [MIT License] diff --git a/TODO.md b/TODO.md index 96656d08..a438d728 100644 --- a/TODO.md +++ b/TODO.md @@ -8,6 +8,9 @@ - Reproc - Subprocess.h - 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 @@ -25,15 +28,21 @@ # Developer Tools - [ ] Doxygen + - Online documentation (read the docs) + - Github pages - [ ] CI/CD - [ ] Codecov - [ ] 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 +- [ ] 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 - spdlog @@ -43,8 +52,11 @@ # Tests - [ ] 100% Line Coverage +- [ ] Improve Branch Coverage - [ ] Benchmark example CMake vs BuildCC -- [ ] Speed profiling `subprocess` vs `std::command` with gprof and qcachegrind +- [ ] 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. # Examples and Demos diff --git a/buildcc/lib/env/include/assert_fatal.h b/buildcc/lib/env/include/assert_fatal.h index 1cac35aa..5233504e 100644 --- a/buildcc/lib/env/include/assert_fatal.h +++ b/buildcc/lib/env/include/assert_fatal.h @@ -9,19 +9,19 @@ namespace buildcc::env { class assert_exception : public std::exception { public: - assert_exception(std::string_view message) : message_(message) {} + assert_exception(const char *const message) : message_(message) {} private: - virtual const char *what() const throw() { return message_.data(); } + virtual const char *what() const throw() { return message_; } private: - std::string_view message_; + const char *const message_; }; -inline void assert_fatal(bool expression, std::string_view message) { +inline void assert_fatal(bool expression, const std::string &message) { if (!expression) { buildcc::env::log_critical("assert", message); - throw assert_exception(message); + throw assert_exception(message.c_str()); } } diff --git a/buildcc/lib/env/include/env.h b/buildcc/lib/env/include/env.h index a332bb27..f6785b89 100644 --- a/buildcc/lib/env/include/env.h +++ b/buildcc/lib/env/include/env.h @@ -9,7 +9,12 @@ namespace fs = std::filesystem; namespace buildcc::env { -// Basic Initialization +/** + * @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(); diff --git a/buildcc/lib/target/include/internal/path.h b/buildcc/lib/target/include/internal/path.h index 2d0c494a..c1169dc3 100644 --- a/buildcc/lib/target/include/internal/path.h +++ b/buildcc/lib/target/include/internal/path.h @@ -22,7 +22,7 @@ class Path { /** * @brief Create a Existing Path object and sets last_write_timstamp to file * timestamp - * NOTE, Throws filesystem exception if file not found + * NOTE, Throws buildcc::env::assert_exception if file not found * * @param pathname * @return Path diff --git a/buildcc/lib/target/include/internal/util.h b/buildcc/lib/target/include/internal/util.h index facb3997..b23ef286 100644 --- a/buildcc/lib/target/include/internal/util.h +++ b/buildcc/lib/target/include/internal/util.h @@ -9,13 +9,22 @@ 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 exception if path does not exist + * Throws buildcc::env::assert_exception if path does not exist * * @param path * @param stored_paths @@ -25,6 +34,14 @@ bool command(const std::vector &command_tokens); 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); diff --git a/buildcc/lib/target/src/target/build.cpp b/buildcc/lib/target/src/target/build.cpp index 1a695d96..363974ab 100644 --- a/buildcc/lib/target/src/target/build.cpp +++ b/buildcc/lib/target/src/target/build.cpp @@ -88,6 +88,7 @@ void Target::BuildRecompile() { 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(); diff --git a/buildcc/lib/target/src/target/source.cpp b/buildcc/lib/target/src/target/source.cpp index dd6ed3ee..be655b96 100644 --- a/buildcc/lib/target/src/target/source.cpp +++ b/buildcc/lib/target/src/target/source.cpp @@ -142,6 +142,9 @@ void Target::RecompileSources() { dirty_ = true; SourceUpdated(); } else { + // TODO, Verify the `physical` presence of object file + + // ELSE // *3 Do nothing dummy_compile_sources.push_back(current_source); } diff --git a/buildcc/src/register.cpp b/buildcc/src/register.cpp index b2229397..8b6a9811 100644 --- a/buildcc/src/register.cpp +++ b/buildcc/src/register.cpp @@ -53,6 +53,7 @@ void Register::Dep(const base::Target &target, const base::Target &dependency) { 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"); } diff --git a/cmake/tool/doxygen.cmake b/cmake/tool/doxygen.cmake new file mode 100644 index 00000000..82401577 --- /dev/null +++ b/cmake/tool/doxygen.cmake @@ -0,0 +1,24 @@ +if (${BUILDCC_DOCUMENTATION}) + find_package(Doxygen + REQUIRED dot + ) + message("Doxygen Found: ${DOXYGEN_FOUND}") + message("Doxygen Version: ${DOXYGEN_VERSION}") + + set(DOXYGEN_EXCLUDE_PATTERNS + *test/* + *mock/* + ) + set(DOXYGEN_BUILTIN_STL_SUPPORT YES) + 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) + 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" + ) +endif() diff --git a/doc/faq/include_dir_vs_header_files.md b/doc/faq/include_dir_vs_header_files.md index aa220bdc..1727c9f9 100644 --- a/doc/faq/include_dir_vs_header_files.md +++ b/doc/faq/include_dir_vs_header_files.md @@ -41,12 +41,6 @@ However when just adding `-I.` to our build we cannot accurately track header fi > Recursively parsing the tree and tracking header files over `-I.` (in this case) would be computationally expensive. -> We might potentially track header files that are not explicitly required by our project. +> We might potentially track header files that are not explicitly required by our project if recursive parsing is used. This can be solved by tracking individual header files per target for rebuilds. - -# Future work - -- Add header file globbing APIs to avoid manually adding each individual header file. -- Add globbing over `Add/Append IncludeDir` API to glob header files pertaining to the directory added. -- Recursive globbing should not be allowed diff --git a/doc/faq/why_this_lib.md b/doc/faq/why_this_lib.md new file mode 100644 index 00000000..f14544cd --- /dev/null +++ b/doc/faq/why_this_lib.md @@ -0,0 +1,83 @@ +# Why _this_ lib + +My opinion on why I chose a particular Third Party library + +> Developers who would like to suggest an alternative library, raise an issue with the **license** and **advantages** clearly outlined. + +# Flatbuffer + +- [Flatbuffers C++ Benchmarks](https://google.github.io/flatbuffers/flatbuffers_benchmarks.html) +- Serialization is needed for optimized rebuilds +- The previous build is compared with the current build + +From [target.fbs](../../buildcc/lib/target/fbs/target.fbs) we can see the different parameters that are tracked + +- source files +- header files +- lib deps (built through buildcc) +- external lib deps (passed in through the `-l` flag for prebuilt libraries) +- include directories +- lib directories +- preprocessor flags +- c compile flags +- cpp compile flags +- link flags + +Also see [Target::BuildRecompile() in build.cpp](../../buildcc/lib/target/src/target/build.cpp) for rechecks + +- Provides helpful utility functions +- `flatbuffer` can be used to generate strict + efficient json + - `flatbuffer` has a few limitations when generation JSON + - For example: an array cannot be a root when writing your schema. +- `flexbuffer` can be used to generate flexibile + efficient JSON. + +# Fmtlib and Spdlog + +- Fmtlib and Spdlog are extremely popular libraries for formatting and logging. +- Fmtlib is also used as a dependency in Spdlog which helps reduce library interdependencies. + +# Taskflow + +- Very efficient and easy to use parallel programming APIs +- Easy to setup dependencies between two or more `Tasks` or `Taskflows` + - This ensures that parts of the build that need to be independent can be built in parallel. + - Automatically uses the C++ Thread library for speedup. +- Provides Graph generation for visualization + +# CLI11 + +- Full feature argument parsing library +- Has support for custom subcommands +- Has support for configuration files in `.toml` or `.ini` format for ease of use +- Also has future support for merging more than one configuration file similar to `Meson build`. [CLI11 issue to merge more than 1 configuration file](https://github.com/CLIUtils/CLI11/issues/486) + +# CppUTest + +- See [Unit-Testing and Mocking list](https://github.com/coder137/build_in_cpp/issues/3) +- Google Test is amazing but Google mock is not as flexible + - To create mocks in Google mock everything must be made virtual in a class + - Google mock can also be intrusive in certain situations. I did not want ANY dependency to testing and mocking frameworks in the core code base. +- Catch2 is another alternative to Google Test, unfortunately it does not have its own mocking framework and would need one of the following. + - Trompeloeil (Compared Catch2 + Trompoliel to the CppUTest suite) + - FFF (More useful to mock out C code) + - FakeIt (Similar to Google mock) + +## Advantages + +- CppUTest and CppUMock is provided as a single package with great compatibility. +- Less number of library dependencies to manage +- CppUMock is extremely customizable + +## Disadvantages + +- CppUTest does not have AS MANY features as Google Test +- CppUMock needs a lot of boilerplate (read _customizable_) + - Thankfully mocking is used very sparingly in the code base + +## Usage + +Unit-Tests and Mocking is used to check the **behaviour** of the core Target class i.e (Compile and Link strategies). + +- CppUTest is used for general assertions +- CppUMock is used for mock out underlying process/system calls and instead verify that the function is being called accurately. +- CppUMock is also used to verify rebuild states and to see if certain parts of the code are being accurately hit (through `expect` APIs). diff --git a/doc/software_architecture/buildcc_core_dep.PNG b/doc/software_architecture/buildcc_core_dep.PNG new file mode 100644 index 00000000..2cab9bd1 Binary files /dev/null and b/doc/software_architecture/buildcc_core_dep.PNG differ diff --git a/doc/software_architecture/generate_cmake_graphviz.md b/doc/software_architecture/generate_cmake_graphviz.md new file mode 100644 index 00000000..a40376af --- /dev/null +++ b/doc/software_architecture/generate_cmake_graphviz.md @@ -0,0 +1,14 @@ +# Generate CMake Graphviz library dependency + +## Core BuildCC dependency +```bash +# Generate BUILDCC_TESTING=OFF +cmake -B _build_graphviz_win --graphviz=_build_graphviz_win/graph.dot +dot -Tpng _build_graphviz_win/graph.dot -o buildcc_core_dep.PNG +``` + +## Core BuildCC and Test dependencies +```bash +cmake -B _build_graphviz_gcc -G Ninja -DBUILDCC_TESTING=ON --graphviz=_build_graphviz_gcc/graph_gcc.dot +dot -Tpng _build_graphviz_gcc/graph_gcc.dot -o buildcc_dep_with_tests.PNG +```