diff --git a/.clang-tidy b/.clang-tidy index eea9376..26eebf2 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -32,7 +32,7 @@ WarningsAsErrors: > bugprone-use-after-move, bugprone-dangling-handle -HeaderFilterRegex: 'src/.*\.(hpp|h)$' +HeaderFilterRegex: '.*/src/.*\.(hpp|h)$' FormatStyle: file diff --git a/.github/workflows/cpp-ci.yml b/.github/workflows/cpp-ci.yml index 4b80fa5..ba725e7 100644 --- a/.github/workflows/cpp-ci.yml +++ b/.github/workflows/cpp-ci.yml @@ -6,43 +6,52 @@ on: pull_request: branches: ["main"] +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: format-check: name: Format Check runs-on: ubuntu-24.04 + timeout-minutes: 5 steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 + with: + persist-credentials: false - name: Check format run: | clang-format --version - find src tests \( -name "*.hpp" -o -name "*.cpp" -o -name "*.h" \) | \ - xargs clang-format --dry-run --Werror + cmake \ + -DCLANG_FORMAT_EXECUTABLE=clang-format \ + -DCLAVIS_SOURCE_DIR="$PWD" \ + -DCLAVIS_FORMAT_CHECK=ON \ + -P cmake/RunClangFormat.cmake build-and-test: name: Build and Test runs-on: ubuntu-24.04 - needs: format-check + timeout-minutes: 15 steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 + with: + persist-credentials: false - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y libgtest-dev + sudo apt-get install --no-install-recommends -y libgtest-dev ninja-build - name: Show tool versions run: | cmake --version g++ --version - - name: Configure - run: cmake --preset ci - - - name: Build - run: cmake --build --preset ci - - - name: Test - run: ctest --preset ci + - name: Build and test + run: cmake --workflow --preset ci diff --git a/.gitignore b/.gitignore index 57721da..7022ed4 100644 --- a/.gitignore +++ b/.gitignore @@ -31,5 +31,6 @@ *.out *.app -# Debug files -build/ \ No newline at end of file +# CMake build artifacts and local presets +build/ +CMakeUserPresets.json diff --git a/CMakeLists.txt b/CMakeLists.txt index d26b8eb..1d8a2df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,93 +2,48 @@ cmake_minimum_required(VERSION 3.25) project(CLAVIS LANGUAGES CXX) -# Set build type (default is Debug) -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug) -endif() - -# NOTE: file(GLOB) is not recommended by CMake official docs. -# Consider listing source files explicitly for reliable rebuilds. -# See: https://cmake.org/cmake/help/latest/command/file.html#glob -file(GLOB CORE_SOURCES - src/*.cpp - src/graph/*.hpp - src/data_structure/*.hpp - src/sorting/*.hpp - src/math/*.hpp -) - -# Collect test files -file(GLOB TEST_SOURCES - tests/*.cpp - tests/graph/*.cpp - tests/data_structure/*.cpp - tests/sorting/*.cpp - tests/math/*.cpp -) +set(CMAKE_CXX_EXTENSIONS OFF) -# Combine all sources -set(SOURCES ${CORE_SOURCES}) - -# Create library -add_library(clavis_algorithm ${SOURCES}) +function(clavis_enable_warnings target) + target_compile_options(${target} PRIVATE + $<$:-Wall -Wextra> + ) +endfunction() -# Set C++ standard (modern target-based approach) +add_library(clavis_algorithm) target_compile_features(clavis_algorithm PUBLIC cxx_std_20) - -# Set compiler warnings (target-based, not global CMAKE_CXX_FLAGS) -target_compile_options(clavis_algorithm PRIVATE - $<$:-Wall -Wextra> -) - -# Set include directories target_include_directories(clavis_algorithm PUBLIC - ${CMAKE_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/src ) +clavis_enable_warnings(clavis_algorithm) -# Google Test configuration -find_package(GTest REQUIRED) -enable_testing() +add_executable(clavis_sorting_example) +target_link_libraries(clavis_sorting_example PRIVATE clavis_algorithm) +clavis_enable_warnings(clavis_sorting_example) -# Test executable -add_executable(clavis_algorithm_test ${TEST_SOURCES}) +add_subdirectory(src) -# Set include directories for tests -target_include_directories(clavis_algorithm_test PRIVATE - ${CMAKE_SOURCE_DIR}/src -) +include(CTest) +if(BUILD_TESTING) + find_package(GTest REQUIRED) -target_link_libraries(clavis_algorithm_test clavis_algorithm GTest::GTest GTest::Main) + add_executable(clavis_algorithm_test) + target_link_libraries(clavis_algorithm_test PRIVATE + clavis_algorithm + GTest::gtest_main + ) + clavis_enable_warnings(clavis_algorithm_test) -# Set compiler warnings for tests -target_compile_options(clavis_algorithm_test PRIVATE - $<$:-Wall -Wextra> -) + add_subdirectory(tests) -# Discover and register tests -include(GoogleTest) -gtest_discover_tests(clavis_algorithm_test) + include(GoogleTest) + gtest_discover_tests(clavis_algorithm_test) +endif() # ============================================================================= # Code Quality Tools # ============================================================================= -# Collect all C++ files for formatting -file(GLOB_RECURSE ALL_CXX_FILES - ${CMAKE_SOURCE_DIR}/src/*.hpp - ${CMAKE_SOURCE_DIR}/src/*.cpp - ${CMAKE_SOURCE_DIR}/src/*.h - ${CMAKE_SOURCE_DIR}/tests/*.cpp -) - -# Collect source files only for linting (exclude tests) -file(GLOB_RECURSE SRC_CXX_FILES - ${CMAKE_SOURCE_DIR}/src/*.hpp - ${CMAKE_SOURCE_DIR}/src/*.cpp - ${CMAKE_SOURCE_DIR}/src/*.h -) - -# clang-format targets find_program(CLANG_FORMAT clang-format HINTS $ENV{LLVM_DIR}/bin @@ -98,13 +53,20 @@ find_program(CLANG_FORMAT clang-format ) if(CLANG_FORMAT) add_custom_target(format - COMMAND ${CLANG_FORMAT} -i ${ALL_CXX_FILES} + COMMAND ${CMAKE_COMMAND} + -DCLANG_FORMAT_EXECUTABLE=${CLANG_FORMAT} + -DCLAVIS_SOURCE_DIR=${PROJECT_SOURCE_DIR} + -P ${PROJECT_SOURCE_DIR}/cmake/RunClangFormat.cmake COMMENT "Running clang-format on all files" VERBATIM ) add_custom_target(format-check - COMMAND ${CLANG_FORMAT} --dry-run --Werror ${ALL_CXX_FILES} + COMMAND ${CMAKE_COMMAND} + -DCLANG_FORMAT_EXECUTABLE=${CLANG_FORMAT} + -DCLAVIS_SOURCE_DIR=${PROJECT_SOURCE_DIR} + -DCLAVIS_FORMAT_CHECK=ON + -P ${PROJECT_SOURCE_DIR}/cmake/RunClangFormat.cmake COMMENT "Checking code format" VERBATIM ) @@ -112,7 +74,15 @@ else() message(STATUS "clang-format not found. Format targets disabled.") endif() -# clang-tidy target +# Lint production targets only; formatting separately covers every C++ file +# under src/ and tests/, including files not yet registered with a target. +get_target_property(CLAVIS_LIBRARY_SOURCES clavis_algorithm SOURCES) +get_target_property(CLAVIS_EXAMPLE_SOURCES clavis_sorting_example SOURCES) +set(CLAVIS_SOURCE_FILES + ${CLAVIS_LIBRARY_SOURCES} + ${CLAVIS_EXAMPLE_SOURCES} +) + find_program(CLANG_TIDY clang-tidy HINTS $ENV{LLVM_DIR}/bin @@ -123,13 +93,12 @@ find_program(CLANG_TIDY clang-tidy if(CLANG_TIDY) add_custom_target(lint COMMAND ${CLANG_TIDY} - -p ${CMAKE_BINARY_DIR} - -header-filter=${CMAKE_SOURCE_DIR}/src/.* - ${SRC_CXX_FILES} + --quiet + ${CLAVIS_SOURCE_FILES} -- -x c++ -std=c++20 - -I${CMAKE_SOURCE_DIR}/src + -I${PROJECT_SOURCE_DIR}/src COMMENT "Running clang-tidy on source files" VERBATIM ) diff --git a/CMakePresets.json b/CMakePresets.json index 3c60293..e89a4bf 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,6 +1,5 @@ { - "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json", - "version": 8, + "version": 6, "cmakeMinimumRequired": { "major": 3, "minor": 25, @@ -13,8 +12,6 @@ "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { - "CMAKE_CXX_STANDARD": "20", - "CMAKE_CXX_STANDARD_REQUIRED": "ON", "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" } }, @@ -38,11 +35,17 @@ "name": "ci", "displayName": "CI", "inherits": "base", - "generator": "Unix Makefiles", - "binaryDir": "${sourceDir}/build", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } + }, + { + "name": "quality", + "displayName": "Code Quality", + "inherits": "base", + "cacheVariables": { + "BUILD_TESTING": "OFF" + } } ], "buildPresets": [ @@ -56,8 +59,22 @@ }, { "name": "ci", - "configurePreset": "ci", - "jobs": 4 + "configurePreset": "ci" + }, + { + "name": "format", + "configurePreset": "quality", + "targets": ["format"] + }, + { + "name": "format-check", + "configurePreset": "quality", + "targets": ["format-check"] + }, + { + "name": "lint", + "configurePreset": "quality", + "targets": ["lint"] } ], "testPresets": [ @@ -86,11 +103,7 @@ { "name": "ci", "configurePreset": "ci", - "inherits": "base", - "output": { - "outputOnFailure": true, - "verbosity": "verbose" - } + "inherits": "base" } ], "workflowPresets": [ diff --git a/README.md b/README.md index 0d273d7..a7a35ef 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Welcome to Clavis! Here, efficient algorithms and unnecessarily complex C++ code | CMake | 3.25+ | `brew install cmake` | | Ninja | any | `brew install ninja` | | GTest | any | `brew install googletest` | -| LLVM | any | `brew install llvm` | +| LLVM | 16+ | `brew install llvm` | ### Quick Setup (macOS) @@ -39,17 +39,21 @@ ctest --preset debug --verbose |--------|-----------|----------| | `debug` | Ninja | Local development | | `release` | Ninja | Production build | -| `ci` | Unix Makefiles | GitHub Actions | +| `ci` | Ninja | GitHub Actions | +| `quality` | Ninja | Code-quality tools | ## Code Quality ```bash +# Configure code-quality tools without requiring GTest +cmake --preset quality + # Format -cmake --build build/debug -t format +cmake --build --preset format # Format check -cmake --build build/debug -t format-check +cmake --build --preset format-check # Lint -cmake --build build/debug -t lint +cmake --build --preset lint ``` diff --git a/cmake/RunClangFormat.cmake b/cmake/RunClangFormat.cmake new file mode 100644 index 0000000..2064cde --- /dev/null +++ b/cmake/RunClangFormat.cmake @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.25) + +if(NOT DEFINED CLANG_FORMAT_EXECUTABLE OR CLANG_FORMAT_EXECUTABLE STREQUAL "") + message(FATAL_ERROR "CLANG_FORMAT_EXECUTABLE is required") +endif() + +if(NOT DEFINED CLAVIS_SOURCE_DIR OR CLAVIS_SOURCE_DIR STREQUAL "") + message(FATAL_ERROR "CLAVIS_SOURCE_DIR is required") +endif() + +# Formatting intentionally discovers files at execution time. Unlike build +# sources, this must also cover new files that are not registered with a target. +file(GLOB_RECURSE CLAVIS_FORMAT_FILES + LIST_DIRECTORIES FALSE + "${CLAVIS_SOURCE_DIR}/src/*.cpp" + "${CLAVIS_SOURCE_DIR}/src/*.h" + "${CLAVIS_SOURCE_DIR}/src/*.hpp" + "${CLAVIS_SOURCE_DIR}/tests/*.cpp" + "${CLAVIS_SOURCE_DIR}/tests/*.h" + "${CLAVIS_SOURCE_DIR}/tests/*.hpp" +) +list(SORT CLAVIS_FORMAT_FILES) + +if(NOT CLAVIS_FORMAT_FILES) + message(FATAL_ERROR "No C++ files found to format") +endif() + +if(CLAVIS_FORMAT_CHECK) + set(CLAVIS_FORMAT_OPTIONS --dry-run --Werror) +else() + set(CLAVIS_FORMAT_OPTIONS -i) +endif() + +execute_process( + COMMAND + "${CLANG_FORMAT_EXECUTABLE}" + ${CLAVIS_FORMAT_OPTIONS} + ${CLAVIS_FORMAT_FILES} + COMMAND_ERROR_IS_FATAL ANY +) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..3831224 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,10 @@ +target_sources(clavis_algorithm PRIVATE + sample.cpp + sample.h +) + +add_subdirectory(data_structure) +add_subdirectory(graph) +add_subdirectory(math) +add_subdirectory(search) +add_subdirectory(sorting) diff --git a/src/data_structure/CMakeLists.txt b/src/data_structure/CMakeLists.txt new file mode 100644 index 0000000..d9f8dc1 --- /dev/null +++ b/src/data_structure/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(clavis_algorithm PRIVATE + binary_search_tree.hpp + fenwick_tree.hpp + max_heap.hpp + segment_tree.hpp + union_find.hpp +) diff --git a/src/graph/CMakeLists.txt b/src/graph/CMakeLists.txt new file mode 100644 index 0000000..46cc78c --- /dev/null +++ b/src/graph/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(clavis_algorithm PRIVATE + bfs.hpp + dfs.hpp + dijkstra.hpp + floyd_warshall.hpp + kruskal.hpp +) diff --git a/src/math/CMakeLists.txt b/src/math/CMakeLists.txt new file mode 100644 index 0000000..b391da5 --- /dev/null +++ b/src/math/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(clavis_algorithm PRIVATE + extended_gcd.hpp + fast_exp.hpp + sieve.hpp +) diff --git a/src/search/CMakeLists.txt b/src/search/CMakeLists.txt new file mode 100644 index 0000000..5ee0cbc --- /dev/null +++ b/src/search/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(clavis_algorithm PRIVATE + binary_search.hpp +) diff --git a/src/sorting/CMakeLists.txt b/src/sorting/CMakeLists.txt new file mode 100644 index 0000000..cbb4489 --- /dev/null +++ b/src/sorting/CMakeLists.txt @@ -0,0 +1,13 @@ +target_sources(clavis_algorithm PRIVATE + bubble_sort.hpp + heap_sort.hpp + merge_sort.hpp + quick_sort.hpp + radix_sort.hpp + shell_sort.hpp + sorting_concepts.hpp +) + +target_sources(clavis_sorting_example PRIVATE + sorting.cpp +) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..f43d282 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,9 @@ +target_sources(clavis_algorithm_test PRIVATE + sample_test.cpp +) + +add_subdirectory(data_structure) +add_subdirectory(graph) +add_subdirectory(math) +add_subdirectory(search) +add_subdirectory(sorting) diff --git a/tests/data_structure/CMakeLists.txt b/tests/data_structure/CMakeLists.txt new file mode 100644 index 0000000..b9c7763 --- /dev/null +++ b/tests/data_structure/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(clavis_algorithm_test PRIVATE + binary_search_tree_test.cpp + fenwick_tree_test.cpp + max_heap_test.cpp + segment_tree_test.cpp + union_find_test.cpp +) diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt new file mode 100644 index 0000000..e0b8e20 --- /dev/null +++ b/tests/graph/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(clavis_algorithm_test PRIVATE + bfs_test.cpp + dfs_test.cpp + dijkstra_test.cpp + floyd_warshall_test.cpp + kruskal_test.cpp +) diff --git a/tests/math/CMakeLists.txt b/tests/math/CMakeLists.txt new file mode 100644 index 0000000..fe3689b --- /dev/null +++ b/tests/math/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(clavis_algorithm_test PRIVATE + extended_gcd_test.cpp + fast_exp_test.cpp + sieve_test.cpp +) diff --git a/tests/search/CMakeLists.txt b/tests/search/CMakeLists.txt new file mode 100644 index 0000000..5d59c05 --- /dev/null +++ b/tests/search/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(clavis_algorithm_test PRIVATE + binary_search_test.cpp +) diff --git a/tests/sorting/CMakeLists.txt b/tests/sorting/CMakeLists.txt new file mode 100644 index 0000000..98b8933 --- /dev/null +++ b/tests/sorting/CMakeLists.txt @@ -0,0 +1,8 @@ +target_sources(clavis_algorithm_test PRIVATE + bubble_sort_test.cpp + heap_sort_test.cpp + merge_sort_test.cpp + quick_sort_test.cpp + radix_sort_test.cpp + shell_sort_test.cpp +)