cmake_minimum_required(VERSION 3.10) project(akstdlib LANGUAGES C) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -ggdb -pg") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -g -ggdb -pg") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -g -ggdb -pg") # Sanitizer build, off by default: # cmake -S . -B build-asan -DAKSL_SANITIZE=ON && ctest --test-dir build-asan # Set before the dependency is added so libakerror is instrumented too -- # several of the defects in TODO.md section 2 (the uninitialised %s in # aksl_realpath, the unbounded vsprintf in aksl_sprintf, the missing va_end in # the printf family) only show up under ASan/UBSan. option(AKSL_SANITIZE "Build the library and its tests with ASan + UBSan" OFF) if(AKSL_SANITIZE) set(AKSL_SANITIZE_FLAGS "-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${AKSL_SANITIZE_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${AKSL_SANITIZE_FLAGS}") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${AKSL_SANITIZE_FLAGS}") message(STATUS "AKSL_SANITIZE=ON: building with ASan + UBSan") endif() if(TARGET akerror::akerror) message(STATUS "FOUND akerror::akerror") else() message(STATUS "MISSING akerror::akerror") endif() include(CTest) include(GNUInstallDirs) include(CMakePackageConfigHelpers) if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) # libakerror registers its own CTest entries unconditionally, and because we # pull it in EXCLUDE_FROM_ALL its test binaries are never built -- so every # one of them used to show up in our suite as "Not Run" and fail. CMake has no # way to un-register a test, and set_tests_properties cannot reach across # directory scopes, so add_test() is shadowed for the duration of the # add_subdirectory() call. The dependency has its own CI; this project's suite # should only contain this project's tests. set(AKSL_SUPPRESS_ADD_TEST TRUE) function(add_test) if(NOT AKSL_SUPPRESS_ADD_TEST) _add_test(${ARGV}) endif() endfunction() # libakerror also marks two of those tests WILL_FAIL, which would now be # setting properties on tests that no longer exist, so suppress that too. function(set_tests_properties) if(NOT AKSL_SUPPRESS_ADD_TEST) _set_tests_properties(${ARGV}) endif() endfunction() add_subdirectory(deps/libakerror EXCLUDE_FROM_ALL) set(AKSL_SUPPRESS_ADD_TEST FALSE) else() if(NOT TARGET akerror::akerror) find_package(PkgConfig REQUIRED) find_package(akerror REQUIRED) endif() endif() set(akstdlib_install_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/akstdlib") set(prefix ${CMAKE_INSTALL_PREFIX}) set(exec_prefix "\${prefix}") set(libdir "\${exec_prefix}/lib") set(includedir "\${prefix}/include") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/akstdlib.pc.in ${CMAKE_CURRENT_BINARY_DIR}/akstdlib.pc @ONLY) add_library(akstdlib SHARED src/stdlib.c ) add_library(akstdlib::akstdlib ALIAS akstdlib) # Specify include directories for the library's headers (if applicable) target_include_directories(akstdlib PUBLIC $ $ ) target_link_libraries(akstdlib PUBLIC akerror::akerror) set(main_lib_dest "lib/my_library-${MY_LIBRARY_VERSION}") install(TARGETS akstdlib EXPORT akstdlibTargets ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) install(FILES "include/akstdlib.h" DESTINATION "include/") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akstdlib.pc DESTINATION "lib/pkgconfig/") install(EXPORT akstdlibTargets FILE akstdlibTargets.cmake NAMESPACE akstdlib:: DESTINATION ${akstdlib_install_cmakedir} ) configure_package_config_file( cmake/akstdlib.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/akstdlibConfig.cmake" INSTALL_DESTINATION ${akstdlib_install_cmakedir} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/akstdlibConfig.cmake" DESTINATION ${akstdlib_install_cmakedir} ) # Each test is one source file tests/test_.c, built into the executable # test_ and registered as the CTest test . Shared helpers live in # tests/aksl_capture.h. # # AKSL_TESTS must exit 0. # AKSL_WILL_FAIL_TESTS expected to abort by design (an unhandled error # reaching FINISH_NORETURN, a deliberate contract # violation), so a non-zero exit is a pass. # AKSL_KNOWN_FAILING_TESTS assert the *correct* behaviour of a confirmed # defect from TODO.md section 2.1. They fail until # the defect is fixed, and are marked WILL_FAIL so # the suite stays green and the gap stays visible. # When one is fixed CTest reports it as failed with # "unexpectedly passed" -- that is the cue to move # it up into AKSL_TESTS. set(AKSL_TESTS linkedlist tree ) set(AKSL_WILL_FAIL_TESTS ) set(AKSL_KNOWN_FAILING_TESTS list_append_chain # TODO.md 2.1.1 -- append truncates lists of 2+ nodes list_iterate_head # TODO.md 2.1.2 -- iterate starts at the midpoint tree_iterate_break # TODO.md 2.1.3 -- ITERATOR_BREAK does not stop a walk ) foreach(_test IN LISTS AKSL_TESTS AKSL_WILL_FAIL_TESTS AKSL_KNOWN_FAILING_TESTS) add_executable(test_${_test} tests/test_${_test}.c) target_include_directories(test_${_test} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests) target_link_libraries(test_${_test} PRIVATE akstdlib) add_test(NAME ${_test} COMMAND test_${_test}) endforeach() if(AKSL_WILL_FAIL_TESTS OR AKSL_KNOWN_FAILING_TESTS) set_tests_properties( ${AKSL_WILL_FAIL_TESTS} ${AKSL_KNOWN_FAILING_TESTS} PROPERTIES WILL_FAIL TRUE ) endif() # Cap every test. The list and tree code is full of loops whose termination # depends on a single condition, so a plausible bug -- or a mutant from the # target below -- turns a test into an infinite loop. Without this, ctest waits # forever; the mutation harness then kills ctest at its own timeout and the # spinning test binary is left orphaned, holding the pipe open and burning a # core. The whole suite runs in well under a second, so 30s is pure headroom. set_tests_properties( ${AKSL_TESTS} ${AKSL_WILL_FAIL_TESTS} ${AKSL_KNOWN_FAILING_TESTS} PROPERTIES TIMEOUT 30 ) # Mutation testing: break the library in small ways and confirm the test suite # notices. This is a meta-check on the tests themselves, and it rebuilds and # re-runs the whole suite once per mutant, so it is a manual target rather than # a CTest test: # cmake --build build --target mutation # This target covers both src/stdlib.c and include/akstdlib.h. CI runs the # narrower, faster src/stdlib.c set with a --threshold gate; see # .gitea/workflows/ci.yaml. find_package(Python3 COMPONENTS Interpreter) if(Python3_FOUND) add_custom_target(mutation COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/mutation_test.py --source-root ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} USES_TERMINAL COMMENT "Running mutation tests (breaks the library, expects tests to fail)" ) endif() # pkgconfig