diff --git a/CMakeLists.txt b/CMakeLists.txt index 3aa1911..b46160d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,50 +1,100 @@ cmake_minimum_required(VERSION 3.20) -project(calculator VERSION 1.0 LANGUAGES C) +project( + calculator + VERSION 1.0 + LANGUAGES C) +# ------------------------------------------------ +# C standard — no compiler extensions +# ------------------------------------------------ set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) -# clangd +# Export compile_commands.json (clangd / IDEs) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -add_compile_options( - -Wall - -Wextra - -Wpedantic -) +# ------------------------------------------------ +# Options +# ------------------------------------------------ +option(CALCULATOR_BUILD_TESTS "Build calculator tests" ON) +option(CALCULATOR_ENABLE_SANITIZERS "Enable Address Sanitizer in tests" ON) -include(cmake/CPM.cmake) +# ------------------------------------------------ +# Portable warning flags +# ------------------------------------------------ +if(MSVC) + add_compile_options(/W4) +else() + add_compile_options(-Wall -Wextra -Wpedantic) +endif() -CPMAddPackage( - NAME arena - GIT_REPOSITORY https://laentropia-homelab.tail7368da.ts.net/laentropia/Arena.git - GIT_TAG main -) +# ================================================================ +# CPM.cmake bootstrap Descarga CPM solo si no existe; usa caché para builds +# offline. +# ================================================================ +set(CPM_DOWNLOAD_VERSION 0.40.2) +set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM.cmake") -CPMAddPackage( - NAME arraylist - GIT_REPOSITORY https://laentropia-homelab.tail7368da.ts.net/laentropia/ArrayList.git - GIT_TAG main -) +if(NOT EXISTS "${CPM_DOWNLOAD_LOCATION}") + message( + STATUS "[calculator] Downloading CPM.cmake v${CPM_DOWNLOAD_VERSION}...") + file( + DOWNLOAD + "https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake" + "${CPM_DOWNLOAD_LOCATION}" + STATUS download_status + TLS_VERIFY ON) + list(GET download_status 0 status_code) + list(GET download_status 1 status_msg) + if(NOT status_code EQUAL 0) + message( + FATAL_ERROR "[calculator] Failed to download CPM.cmake: ${status_msg}") + endif() +endif() -add_library(calculator_lib - src/lexer.c - src/parser.c - src/evaluator.c -) +include("${CPM_DOWNLOAD_LOCATION}") -target_include_directories(calculator_lib - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include -) +# ================================================================ +# Dependencies via CPM +# ================================================================ +cpmaddpackage( + NAME arena GIT_REPOSITORY + https://laentropia-homelab.tail7368da.ts.net/laentropia/Arena.git GIT_TAG + main) -target_link_libraries(calculator_lib - PUBLIC arena - PUBLIC arraylist - PRIVATE m -) +cpmaddpackage( + NAME arraylist GIT_REPOSITORY + https://laentropia-homelab.tail7368da.ts.net/laentropia/ArrayList.git GIT_TAG + main) +# ================================================================ +# LIBRARY (lógica reutilizable, separada del ejecutable) +# ================================================================ +add_library(calculator_lib src/lexer.c src/parser.c src/evaluator.c) + +target_include_directories( + calculator_lib PUBLIC $ + $) + +target_compile_features(calculator_lib PUBLIC c_std_11) + +target_link_libraries( + calculator_lib + PUBLIC arena + PUBLIC arraylist + PRIVATE m) # libm — solo en el linker interno, no se propaga + +# ================================================================ +# EXECUTABLE +# ================================================================ add_executable(calculator src/main.c) -target_link_libraries(calculator calculator_lib) +target_link_libraries(calculator PRIVATE calculator_lib) -enable_testing() -add_subdirectory(test) +# ================================================================ +# TESTS +# ================================================================ +if(CALCULATOR_BUILD_TESTS) + enable_testing() + add_subdirectory(test) +endif() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fba3b31..fad601a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,24 +1,114 @@ -find_package(cmocka REQUIRED) +# ================================================================ +# FIND CMOCKA (system → FetchContent as fallback) +# ================================================================ +find_package(cmocka QUIET) -add_executable(test_lexer test_lexer.c) -add_executable(test_parser test_parser.c) -add_executable(test_evaluator test_evaluator.c) +if(NOT cmocka_FOUND) + message( + STATUS + "[calculator] cmocka not found on system — fetching with FetchContent...") -target_link_libraries(test_lexer - calculator_lib - cmocka::cmocka -) + include(FetchContent) + FetchContent_Declare( + cmocka + GIT_REPOSITORY https://git.cryptomilk.org/projects/cmocka.git + GIT_TAG cmocka-1.1.7 + GIT_SHALLOW TRUE) -target_link_libraries(test_parser - calculator_lib - cmocka::cmocka -) + # Static lib only, no cmocka examples or self-tests + set(WITH_STATIC_LIB + ON + CACHE BOOL "" FORCE) + set(WITH_SHARED_LIB + OFF + CACHE BOOL "" FORCE) + set(WITH_CMOCKERY_SUPPORT + OFF + CACHE BOOL "" FORCE) + set(WITH_EXAMPLES + OFF + CACHE BOOL "" FORCE) + set(UNIT_TESTING + OFF + CACHE BOOL "" FORCE) + set(PICKY_DEVELOPER + OFF + CACHE BOOL "" FORCE) -target_link_libraries(test_evaluator - calculator_lib - cmocka::cmocka -) + FetchContent_MakeAvailable(cmocka) -add_test(NAME lexer_tests COMMAND test_lexer) -add_test(NAME parser_tests COMMAND test_parser) -add_test(NAME evaluator_tests COMMAND test_evaluator) + # Normalize the target name (varies across cmocka versions) + if(NOT TARGET cmocka::cmocka) + if(TARGET cmocka-static) + add_library(cmocka::cmocka ALIAS cmocka-static) + elseif(TARGET cmocka) + add_library(cmocka::cmocka ALIAS cmocka) + else() + message( + WARNING "[calculator] Could not create cmocka::cmocka — skipping tests." + ) + return() + endif() + endif() +endif() + +# ================================================================ +# DETECT ADDRESS SANITIZER SUPPORT +# ================================================================ +set(CALCULATOR_USE_ASAN OFF) + +if(CALCULATOR_ENABLE_SANITIZERS) + if(MSVC) + if(MSVC_VERSION GREATER_EQUAL 1928) + set(CALCULATOR_USE_ASAN ON) + else() + message(STATUS "[calculator] MSVC < 16.9: ASAN not available.") + endif() + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang" AND NOT WIN32) + include(CheckCCompilerFlag) + check_c_compiler_flag(-fsanitize=address CALCULATOR_COMPILER_HAS_ASAN) + if(CALCULATOR_COMPILER_HAS_ASAN) + set(CALCULATOR_USE_ASAN ON) + else() + message( + STATUS "[calculator] Compiler does not support -fsanitize=address.") + endif() + else() + message(STATUS "[calculator] Unknown platform/compiler — ASAN skipped.") + endif() + + if(CALCULATOR_USE_ASAN) + message(STATUS "[calculator] Address Sanitizer enabled.") + endif() +endif() + +# ================================================================ +# Helper: apply common settings to every test target +# ================================================================ +function(calculator_add_test target source) + add_executable(${target} ${source}) + + target_link_libraries(${target} PRIVATE calculator_lib cmocka::cmocka) + + target_compile_features(${target} PRIVATE c_std_11) + + if(CALCULATOR_USE_ASAN) + if(MSVC) + target_compile_options(${target} PRIVATE /fsanitize=address) + else() + target_compile_options(${target} PRIVATE -fsanitize=address + -fno-omit-frame-pointer) + target_link_options(${target} PRIVATE -fsanitize=address) + endif() + endif() + + add_test(NAME ${target} COMMAND ${target}) + set_tests_properties(${target} PROPERTIES TIMEOUT 30) +endfunction() + +# ================================================================ +# TEST TARGETS +# ================================================================ +calculator_add_test(test_lexer test_lexer.c) +calculator_add_test(test_parser test_parser.c) +calculator_add_test(test_evaluator test_evaluator.c)