cmake_minimum_required(VERSION 3.10) project(webserv) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # Enable testing enable_testing() # Enable parallel compilation include(ProcessorCount) ProcessorCount(N) if(NOT N EQUAL 0) set(CMAKE_BUILD_PARALLEL_LEVEL ${N}) endif() # Add source files file(GLOB_RECURSE SOURCES "${PROJECT_SOURCE_DIR}/webserv/*.cpp" ) # Remove main.cpp from sources for library (we'll add it back for the main executable) list(FILTER SOURCES EXCLUDE REGEX ".*main\\.cpp$") # Add include directories include_directories( ${PROJECT_SOURCE_DIR} ) # Build type options option(ENABLE_COVERAGE "Enable code coverage" OFF) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() # Define available build types for IDE set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "ASAN" "RelWithDebInfo" "MinSizeRel" ) message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") # Coverage configuration if(ENABLE_COVERAGE) message(STATUS "Code coverage enabled") # Find required tools find_program(GCOV_PATH gcov) find_program(LCOV_PATH lcov) find_program(GENHTML_PATH genhtml) find_program(GCOVR_PATH gcovr) if(NOT GCOV_PATH) message(FATAL_ERROR "gcov not found! Please install gcov. On Ubuntu: sudo apt-get install gcc") endif() # Check for coverage tools (prefer lcov, fallback to gcovr) if(LCOV_PATH AND GENHTML_PATH) message(STATUS "Found lcov and genhtml - using lcov for coverage reports") set(COVERAGE_TOOL "lcov") elseif(GCOVR_PATH) message(STATUS "Found gcovr - using gcovr for coverage reports") set(COVERAGE_TOOL "gcovr") else() message(STATUS "Coverage tools not found. Install with:") message(STATUS " Ubuntu: sudo apt-get install lcov") message(STATUS " Or: pip install gcovr") message(STATUS "Coverage data will still be generated, but no HTML report") set(COVERAGE_TOOL "none") endif() # Set coverage flags based on compiler if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") message(STATUS "Using GCC coverage flags") set(COVERAGE_FLAGS "--coverage -fprofile-arcs -ftest-coverage -fno-inline -fno-inline-small-functions -fno-default-inline") set(COVERAGE_LINK_FLAGS "--coverage") elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") message(STATUS "Using Clang coverage flags") # Use --coverage for Clang to generate gcov-compatible data set(COVERAGE_FLAGS "--coverage -fprofile-arcs -ftest-coverage") set(COVERAGE_LINK_FLAGS "--coverage") else() message(FATAL_ERROR "Code coverage only supported with GCC or Clang") endif() # Apply coverage flags set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${COVERAGE_LINK_FLAGS}") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${COVERAGE_LINK_FLAGS}") # Ensure debug information is available set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0") endif() # Set build flags for different build types if(CMAKE_BUILD_TYPE STREQUAL "Debug") message(STATUS "Debug build: adding debug flags") add_compile_options(-g -O0) add_definitions(-DDEBUG) elseif(CMAKE_BUILD_TYPE STREQUAL "Release") message(STATUS "Release build: adding optimization flags") # -Wpedantic -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wunused -Woverloaded-virtual -Wconversion -Wsign-conversion -Wnull-dereference -Wdouble-promotion -Wformat=2 add_compile_options(-O2 -g -Wall -Wextra -Werror) add_definitions(-DNDEBUG) elseif(CMAKE_BUILD_TYPE STREQUAL "ASAN") message(STATUS "AddressSanitizer build: adding sanitizer flags") add_compile_options(-g -O1 -fsanitize=address -fno-omit-frame-pointer) add_link_options(-fsanitize=address) add_definitions(-DASAN) endif() # Add executable target add_executable(webserv ${SOURCES} "${PROJECT_SOURCE_DIR}/webserv/main.cpp") # Create a library for testing (without main.cpp) add_library(webserv_lib ${SOURCES}) # Google Test integration option(BUILD_TESTS "Build tests" ON) if(BUILD_TESTS) find_package(PkgConfig QUIET) if(PkgConfig_FOUND) pkg_check_modules(GTEST gtest) pkg_check_modules(GTEST_MAIN gtest_main) endif() if(GTEST_FOUND AND GTEST_MAIN_FOUND) message(STATUS "Using system Google Test") # Use system gtest - variables will be set by pkg_check_modules else() message(STATUS "Downloading Google Test") include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip ) # For Windows: Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) # Make sure gtest targets are available if(TARGET gtest_main) message(STATUS "Google Test targets available") endif() endif() # Add test directory if it exists if(EXISTS "${CMAKE_SOURCE_DIR}/tests") add_subdirectory(tests) else() message(STATUS "Tests directory not found, creating basic test structure") file(MAKE_DIRECTORY "${CMAKE_SOURCE_DIR}/tests") # Create a basic test file file(WRITE "${CMAKE_SOURCE_DIR}/tests/test_main.cpp" "#include // Basic test to verify Google Test is working TEST(BasicTest, TruthTest) { EXPECT_TRUE(true); EXPECT_FALSE(false); EXPECT_EQ(1 + 1, 2); } int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ") # Create CMakeLists.txt for tests file(WRITE "${CMAKE_SOURCE_DIR}/tests/CMakeLists.txt" "# Test executable add_executable(webserv_tests test_main.cpp ) # Link with Google Test if(TARGET gtest_main) target_link_libraries(webserv_tests gtest_main webserv_lib ) else() target_link_libraries(webserv_tests \${GTEST_LIBRARIES} \${GTEST_MAIN_LIBRARIES} webserv_lib ) target_include_directories(webserv_tests PRIVATE \${GTEST_INCLUDE_DIRS}) endif() target_include_directories(webserv_tests PRIVATE \${CMAKE_SOURCE_DIR} \${CMAKE_SOURCE_DIR}/webserv ) # Discover tests include(GoogleTest) gtest_discover_tests(webserv_tests) ") endif() endif() # Coverage targets if(ENABLE_COVERAGE AND BUILD_TESTS) # Coverage cleanup target add_custom_target(coverage_clean COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/coverage_report COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_BINARY_DIR}/coverage.info COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_BINARY_DIR}/coverage.xml COMMENT "Cleaning coverage data" ) if(COVERAGE_TOOL STREQUAL "lcov") # Main coverage target using lcov add_custom_target(coverage DEPENDS webserv_tests # Cleanup previous coverage data COMMAND ${LCOV_PATH} --directory . --zerocounters # Run tests COMMAND ${CMAKE_CTEST_COMMAND} --verbose # Capture coverage data COMMAND ${LCOV_PATH} --directory . --capture --output-file coverage.info # Remove system and test files from coverage COMMAND ${LCOV_PATH} --remove coverage.info '/usr/*' '/opt/*' '*/tests/*' '*/test/*' '*/_deps/*' '*/build/*' --output-file coverage.info # Generate HTML report COMMAND ${GENHTML_PATH} coverage.info --output-directory coverage_report --title "Webserv Coverage Report" --show-details --legend # Print summary COMMAND ${LCOV_PATH} --summary coverage.info WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Generating code coverage report with lcov" ) # Coverage summary target add_custom_target(coverage_summary DEPENDS webserv_tests COMMAND ${CMAKE_CTEST_COMMAND} --verbose COMMAND ${LCOV_PATH} --directory . --capture --output-file coverage.info COMMAND ${LCOV_PATH} --remove coverage.info '/usr/*' '/opt/*' '*/tests/*' '*/test/*' '*/_deps/*' '*/build/*' --output-file coverage.info COMMAND ${LCOV_PATH} --summary coverage.info WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Generating coverage summary" ) elseif(COVERAGE_TOOL STREQUAL "gcovr") # Coverage target using gcovr add_custom_target(coverage DEPENDS webserv_tests # Run tests COMMAND ${CMAKE_CTEST_COMMAND} --verbose # Generate HTML report COMMAND ${GCOVR_PATH} --root ${CMAKE_SOURCE_DIR} --exclude-unreachable-branches --exclude '.*test.*' --exclude '.*_deps.*' --html --html-details -o coverage_report.html # Generate XML report (for CI) COMMAND ${GCOVR_PATH} --root ${CMAKE_SOURCE_DIR} --exclude-unreachable-branches --exclude '.*test.*' --exclude '.*_deps.*' --xml -o coverage.xml # Print summary to console COMMAND ${GCOVR_PATH} --root ${CMAKE_SOURCE_DIR} --exclude-unreachable-branches --exclude '.*test.*' --exclude '.*_deps.*' WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Generating code coverage report with gcovr" ) # Coverage summary target add_custom_target(coverage_summary DEPENDS webserv_tests COMMAND ${CMAKE_CTEST_COMMAND} --verbose COMMAND ${GCOVR_PATH} --root ${CMAKE_SOURCE_DIR} --exclude-unreachable-branches --exclude '.*test.*' --exclude '.*_deps.*' WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Generating coverage summary" ) else() # Basic coverage target without report generation add_custom_target(coverage DEPENDS webserv_tests COMMAND ${CMAKE_CTEST_COMMAND} --verbose COMMAND ${CMAKE_COMMAND} -E echo "Coverage data generated. Install lcov or gcovr to generate HTML reports." WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Running tests with coverage enabled" ) endif() # Add coverage dependencies if(TARGET coverage) add_dependencies(coverage webserv_tests) endif() message(STATUS "Coverage targets available:") message(STATUS " make coverage - Run tests and generate coverage report") message(STATUS " make coverage_summary - Run tests and show coverage summary") message(STATUS " make coverage_clean - Clean coverage data") endif()