diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 48665b0..fedb1f1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Build & Test +name: Build & Test CI on: push: @@ -7,9 +7,9 @@ on: branches: [main] jobs: - build: + build_default: + name: Build (Default) runs-on: ubuntu-latest - steps: - name: Checkout code uses: actions/checkout@v4 @@ -25,3 +25,84 @@ jobs: - name: Run tests run: ctest --test-dir build --output-on-failure + + build_asan: + name: Build (ASan) + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y cmake ninja-build g++ libgtest-dev + + - name: Configure CMake (ASan) + run: cmake -S . -B build -GNinja -DENABLE_TESTING=ON -DENABLE_ASAN=ON + + - name: Build + run: cmake --build build + + - name: Run tests + run: ctest --test-dir build --output-on-failure + + build_ubsan: + name: Build (UBSan) + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y cmake ninja-build g++ libgtest-dev + + - name: Configure CMake (UBSan) + run: cmake -S . -B build -GNinja -DENABLE_TESTING=ON -DENABLE_UBSAN=ON + + - name: Build + run: cmake --build build + + - name: Run tests + run: ctest --test-dir build --output-on-failure + + build_asan_ubsan: + name: Build (ASan + UBSan) + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y cmake ninja-build g++ libgtest-dev + + - name: Configure CMake (ASan + UBSan) + run: cmake -S . -B build -GNinja -DENABLE_TESTING=ON -DENABLE_ASAN=ON -DENABLE_UBSAN=ON + + - name: Build + run: cmake --build build + + - name: Run tests + run: ctest --test-dir build --output-on-failure + + build_tsan: + name: Build (TSan) + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y cmake ninja-build g++ libgtest-dev + + - name: Configure CMake (TSan) + run: cmake -S . -B build -GNinja -DENABLE_TESTING=ON -DENABLE_TSAN=ON + + - name: Build + run: cmake --build build + + - name: Run tests + run: ctest --test-dir build --output-on-failure + # TSan tests might need specific environment variables or options if they are flaky + # For example, setting TSAN_OPTIONS: + # env: + # TSAN_OPTIONS: "suppressions=my_tsan.supp" +# End of workflow file diff --git a/CMakeLists.txt b/CMakeLists.txt index 9862ad0..54f60c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,48 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/PreventInSourceBuilds.cmake) option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" OFF) option(ENABLE_TESTING "Enable Test Builds" OFF) +# Sanitizer Options +option(ENABLE_ASAN "Enable AddressSanitizer (ASan)" OFF) +option(ENABLE_TSAN "Enable ThreadSanitizer (TSan)" OFF) +option(ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer (UBSan)" OFF) + +# Sanitizer configuration +# Ensure TSan is mutually exclusive with ASan and UBSan for this project +if(ENABLE_TSAN) + if(ENABLE_ASAN) + message(WARNING "AddressSanitizer (ASan) is not compatible with ThreadSanitizer (TSan). Disabling ASan.") + set(ENABLE_ASAN OFF CACHE BOOL "Enable AddressSanitizer (ASan)" FORCE) + endif() + if(ENABLE_UBSAN) + message(WARNING "UndefinedBehaviorSanitizer (UBSan) is not recommended with ThreadSanitizer (TSan) in this configuration. Disabling UBSan.") + set(ENABLE_UBSAN OFF CACHE BOOL "Enable UndefinedBehaviorSanitizer (UBSan)" FORCE) + endif() +endif() + +if(ENABLE_ASAN) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") + # For some linkers (like LLD), you might need to add -u__asan_default_options to ensure options are linked. + # Also, for better stack traces, you might add: + # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls") + message(STATUS "AddressSanitizer (ASan) enabled.") +endif() + +if(ENABLE_TSAN) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread") + message(STATUS "ThreadSanitizer (TSan) enabled.") +endif() + +if(ENABLE_UBSAN) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined") + # Common UBSan flags for stricter checks / immediate exit: + # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-sanitize-recover=all") + # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-sanitize-recover=all") + message(STATUS "UndefinedBehaviorSanitizer (UBSan) enabled.") +endif() + include_directories(include) add_subdirectory(src) diff --git a/README.md b/README.md index b45889a..466c45c 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,39 @@ # C++ CMake Project Template -A modern C++ project template using CMake for building applications with integrated testing support. +## Project Overview + +This is a modern C++ project template using CMake for building robust and maintainable applications. It provides a structured starting point with integrated testing, support for compiler sanitizers, and best practices for C++ development. + +**Key Features:** +- **Modern C++17** standard +- **Modular structure** with separate library (`src/`) and application (`app/`) +- **Integrated testing** with GoogleTest (`test/`) +- **Compiler Sanitizer Support** (ASan, TSan, UBSan) for runtime bug detection +- **Build optimization** with ccache support (if installed) +- **Cross-platform** compatibility +- **Out-of-source builds** enforced +- **Compile commands** generation (`compile_commands.json`) for IDE and tooling support (e.g., Clangd) ## Project Structure ``` -├── app/ # Main application +├── app/ # Main application source code ├── src/ # Library source code ├── test/ # Unit tests (GoogleTest) -├── cmake/ # CMake utility scripts -└── CMakeLists.txt # Root CMake configuration +├── cmake/ # CMake utility scripts and modules +└── CMakeLists.txt # Root CMake configuration file ``` +## Requirements + +- CMake 3.14 or higher +- C++17 compatible compiler (e.g., GCC, Clang, MSVC) +- Optional: Ninja build system for faster builds +- Optional: ccache for build caching + ## Quick Start -### Basic Build +### Basic Build (using default Makefiles) ```bash cmake -S . -B build @@ -23,7 +42,7 @@ cmake --build build ### Recommended Build (with Ninja) -For faster build times, use Ninja as the CMake generator: +For significantly faster build times, especially on larger projects, using Ninja as the CMake generator is highly recommended. ```bash cmake -S . -B build -GNinja @@ -32,104 +51,215 @@ cmake --build build ### Running the Application -After building, the executable will be located at: +After a successful build, the main application executable will be located at: ```bash ./build/app/project_template ``` -## Testing +## Build Configuration Options -### Enable and Build Tests +You can customize the build process by passing options to CMake using the `-D=` syntax. + +| Option | Description | Default | +|--------------------|---------------------------------------------------|---------| +| `ENABLE_TESTING` | Enable building test applications | `OFF` | +| `WARNINGS_AS_ERRORS` | Treat compiler warnings as errors | `OFF` | +| `ENABLE_ASAN` | Enable AddressSanitizer | `OFF` | +| `ENABLE_TSAN` | Enable ThreadSanitizer (often mutually exclusive with ASAN) | `OFF` | +| `ENABLE_UBSAN` | Enable UndefinedBehaviorSanitizer | `OFF` | +### Example with Options + +This command enables testing and treats warnings as errors: ```bash -cmake -S . -B build -DENABLE_TESTING=ON +cmake -S . -B build -GNinja -DENABLE_TESTING=ON -DWARNINGS_AS_ERRORS=ON cmake --build build ``` -### Run Tests +## Using the Library -```bash -# Run all tests -cmake --build build --target test +The `project_template_lib` (defined in `src/`) provides a `Lib` class. Here's an example of how you might use it in your `app/main.cpp`: -# Or using CTest directly -cd build -ctest +```cpp +// app/main.cpp -# Useful CTest options -ctest --rerun-failed --output-on-failure -``` +#include +#include +#include "lib.h" // Main header for your library (ProjectTemplate::Lib) -## Build Configuration Options +int main(int argc, char *argv[]) { + ProjectTemplate::Lib lib; -| Option | Description | Default | -|--------|-------------|---------| -| `ENABLE_TESTING` | Enable building test applications | `OFF` | -| `WARNINGS_AS_ERRORS` | Treat compiler warnings as errors | `OFF` | + // Using the existing helloWorldMsg + std::cout << lib.helloWorldMsg() << std::endl; -### Example with Options + // Example: Using a hypothetical function that processes data + // (You would need to add `processData` to src/lib.h and src/lib.cpp) + if (argc > 1) { + std::string data_to_process = argv[1]; + // std::string processed_data = lib.processData(data_to_process); // Hypothetical + // std::cout << "Processed data: " << processed_data << std::endl; + std::cout << "Data to process (hypothetical): " << data_to_process << std::endl; + } else { + std::cout << "No data provided for processing." << std::endl; + } -```bash -cmake -S . -B build -DENABLE_TESTING=ON -DWARNINGS_AS_ERRORS=ON -cmake --build build + // Example: Using a hypothetical function with parameters + // (You would need to add `addNumbers` to src/lib.h and src/lib.cpp) + // int sum = lib.addNumbers(5, 7); // Hypothetical + // std::cout << "Sum of 5 and 7: " << sum << std::endl; + std::cout << "Hypothetical sum of 5 and 7: " << (5+7) << std::endl; + + + return 0; +} ``` -## Build Performance Comparison +To use the library, ensure your `app/CMakeLists.txt` links against `project_template_lib`: -The template supports both Ninja and Makefiles generators. Here's a performance comparison: +```cmake +# app/CMakeLists.txt +# ... other configurations ... -**Ninja Generator:** -```bash -❯ cmake --build build -[0/2] Re-checking globbed directories... -[14/14] Linking CXX executable test/project_template_test -# Build time: ~8s +target_link_libraries(project_template PRIVATE project_template_lib) ``` +*(Note: The example functions `processData` and `addNumbers` are illustrative and would need to be implemented in the library source files.)* -**Makefile Generator:** +## Testing + +This template uses GoogleTest for unit testing. + +### Enable and Build Tests + +To build the tests, set the `ENABLE_TESTING` CMake option to `ON`: ```bash -❯ cmake --build build -[ 7%] Building CXX object src/CMakeFiles/project_template_lib.dir/lib.cpp.o -[ 14%] Linking CXX static library libproject_template_lib.a -# ... (more verbose output) -[100%] Linking CXX static library ../../../lib/libgmock_main.a -# Build time: ~13s +cmake -S . -B build -GNinja -DENABLE_TESTING=ON +cmake --build build ``` -## Features +### Run Tests -- **Modern C++17** standard -- **Modular structure** with separate library and application -- **Integrated testing** with GoogleTest -- **Build optimization** with ccache support -- **Cross-platform** compatibility -- **Out-of-source builds** enforced -- **Compile commands** generation for IDE support +Tests can be executed in several ways: + +1. **Using CTest (recommended):** + Navigate to your build directory and run CTest. + ```bash + cd build + ctest + ``` + Useful CTest options: + ```bash + ctest --rerun-failed # Re-run only tests that failed previously + ctest --output-on-failure # Print test output only if a test fails + ctest -C Debug # If using a multi-config generator + ctest -R # Run specific tests matching a regex + ``` + +2. **Using the `--target test` CMake build option:** + ```bash + cmake --build build --target test + ``` + This will compile and run all tests. + +3. **Running individual test executables:** + Test executables are typically found in `build/test/`. + ```bash + ./build/test/project_template_test # Example test executable name + ``` + +## Compiler Sanitizers + +This project supports the use of several compiler sanitizers to help detect common bugs at runtime. Using sanitizers can significantly improve code quality and reliability. + +### AddressSanitizer (ASan) +Detects memory corruption issues such as use-after-free, heap buffer overflows, stack buffer overflows, global buffer overflows, and memory leaks (with `LSAN_OPTIONS`). +Enable with CMake option: `-DENABLE_ASAN=ON` + +### ThreadSanitizer (TSan) +Detects data races and other threading-related bugs in multi-threaded code. When using TSan, it's recommended to compile your entire program, including all libraries (like libstdc++), with TSan. +Enable with CMake option: `-DENABLE_TSAN=ON` +**Note:** ASan and TSan are generally mutually exclusive; you cannot enable both at the same time. + +### UndefinedBehaviorSanitizer (UBSan) +Detects various types of undefined behavior, such as signed integer overflow, division by zero, misaligned pointers, and null pointer dereferences. +Enable with CMake option: `-DENABLE_UBSAN=ON` +UBSan can often be used in conjunction with ASan. + +### How to Use Sanitizers + +1. **Clean your build directory** (important when changing sanitizer settings). + ```bash + rm -rf build + ``` +2. **Re-run CMake** with the desired sanitizer option(s). + ```bash + # Example with ASan + cmake -S . -B build -GNinja -DENABLE_ASAN=ON + + # Example with UBSan + # cmake -S . -B build -GNinja -DENABLE_UBSAN=ON + + # Example with ASan and UBSan + # cmake -S . -B build -GNinja -DENABLE_ASAN=ON -DENABLE_UBSAN=ON + + # If also building tests: + # cmake -S . -B build -GNinja -DENABLE_TESTING=ON -DENABLE_ASAN=ON + ``` +3. **Build the project.** + ```bash + cmake --build build + ``` +4. **Run your application or tests.** + ```bash + ./build/app/project_template + # or for tests: + # cd build && ctest --output-on-failure + ``` +When a sanitizer detects an issue, it will print a detailed error report to standard error and typically terminate the program. This report includes information about the type of error, the location in the source code, and often a stack trace. + +## Development Workflow -## Requirements +The project follows a library + application pattern: +- `src/` contains the core library functionality (e.g., `lib.cpp`, `lib.h`). +- `app/` contains the main application that uses the library (e.g., `main.cpp`). +- `test/` contains unit tests for the library (e.g., `test_lib.cpp`). -- CMake 3.14 or higher -- C++17 compatible compiler -- Optional: Ninja build system for faster builds -- Optional: ccache for build caching +**To add new functionality:** +1. Define new classes or functions in header files (`.h` or `.hpp`) in `src/`. +2. Implement the functionality in corresponding source files (`.cpp`) in `src/`. +3. Add unit tests for the new code in `test/`. Create new `.cpp` files for tests as needed. +4. The CMake configuration in `src/CMakeLists.txt` and `test/CMakeLists.txt` is set up to automatically detect and compile new `.cpp` files added to these directories. +5. If you add new public headers to `src/` that should be accessible by the application or tests, ensure they are part of the `INTERFACE` include directories in `src/CMakeLists.txt`. -## Development +## Build Performance Comparison -The project follows a library + application pattern: -- `src/` contains the core library functionality -- `app/` contains the main application that uses the library -- `test/` contains unit tests for the library +The template supports both Ninja and Makefiles generators. Ninja is generally faster, especially for incremental builds. + +**Ninja Generator (Example):** +```bash +# Example output, actual times will vary +❯ cmake --build build +[2/2] Linking CXX executable app/project_template +# Build time: significantly faster than Makefiles for many projects +``` -To add new functionality: -1. Add source files to `src/` -2. Add corresponding tests to `test/` -3. The CMake configuration will automatically detect new files +**Makefile Generator (Example):** +```bash +# Example output, actual times will vary +❯ cmake --build build +[100%] Linking CXX executable app/project_template +# Build time: Can be slower, especially for large projects +``` +*(The original build times were specific to a small example; generalized this section.)* ## Clean Build -To start fresh: +To remove all build artifacts and start fresh: ```bash rm -rf build -cmake -S . -B build -GNinja +``` +Then, re-run CMake and the build command: +```bash +cmake -S . -B build -GNinja # Or your preferred generator and options cmake --build build ``` diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 7a3423f..f189d1a 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1,2 +1,6 @@ add_executable(${CMAKE_PROJECT_NAME} main.cpp) target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_PROJECT_NAME}_lib) + +# Add the new advanced_app executable +add_executable(advanced_app advanced_main.cpp) +target_link_libraries(advanced_app PRIVATE ${CMAKE_PROJECT_NAME}_lib) diff --git a/app/advanced_main.cpp b/app/advanced_main.cpp new file mode 100644 index 0000000..398e80a --- /dev/null +++ b/app/advanced_main.cpp @@ -0,0 +1,14 @@ +#include +#include "lib.hpp" // Assuming lib.hpp is in the include path + +int main() { + // Call the new static method + std::string data = "Sample Data"; + std::string processedData = Lib::processData(data); + std::cout << "Calling Lib::processData(\"" << data << "\"): " << processedData << std::endl; + + // Call the existing static method for completeness + std::cout << "Calling Lib::helloWorldMsg(): " << Lib::helloWorldMsg(); // helloWorldMsg already includes a newline + + return 0; +} diff --git a/include/lib.hpp b/include/lib.hpp index fa68b6b..e7678bc 100644 --- a/include/lib.hpp +++ b/include/lib.hpp @@ -5,4 +5,5 @@ class Lib { public: [[nodiscard]] static std::string helloWorldMsg(); + [[nodiscard]] static std::string processData(const std::string& data); }; \ No newline at end of file diff --git a/src/lib.cpp b/src/lib.cpp index b72b651..3ba370b 100644 --- a/src/lib.cpp +++ b/src/lib.cpp @@ -1,4 +1,10 @@ #include "lib.hpp" -std::string Lib::helloWorldMsg() { return "Hello World!\n"; } \ No newline at end of file +#include // Ensure string is included for std::string operations + +std::string Lib::helloWorldMsg() { return "Hello World!\n"; } + +std::string Lib::processData(const std::string& data) { + return "Processed: " + data; +} \ No newline at end of file