Cover control map capacity, the default binding set, keyboard and gamepad dispatch including cross-device rejection, the dpad handlers, and device add and remove against the dummy SDL drivers. Synthesize SDL events directly rather than pumping the event queue, so the suite needs no real hardware. Fix the three-way appstate check in the four gamepad handlers, which tested appstate in place of both the event pointer and the looked-up player actor, so a null event or a missing player was dereferenced instead of reported. Record the new coverage status in TODO.md along with the seven defects the suites uncovered and fixed, eleven that remain open, and the reason the build tree has to precede LD_LIBRARY_PATH for the CTest run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
312 lines
11 KiB
CMake
312 lines
11 KiB
CMake
cmake_minimum_required(VERSION 3.10)
|
|
project(akgl LANGUAGES C)
|
|
|
|
include(CTest)
|
|
option(AKGL_COVERAGE "Instrument libakgl and generate coverage reports with CTest" OFF)
|
|
|
|
if(AKGL_COVERAGE)
|
|
if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
|
|
message(FATAL_ERROR "AKGL_COVERAGE requires GCC or Clang")
|
|
endif()
|
|
find_program(GCOVR_EXECUTABLE gcovr REQUIRED)
|
|
endif()
|
|
|
|
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
|
|
|
# Vendored projects own their test suites. Suppress their CTest registration
|
|
# while embedded so the top-level suite contains only targets built here.
|
|
set(AKGL_SUPPRESS_DEPENDENCY_TESTS TRUE)
|
|
function(add_test)
|
|
if(NOT AKGL_SUPPRESS_DEPENDENCY_TESTS)
|
|
_add_test(${ARGV})
|
|
endif()
|
|
endfunction()
|
|
function(set_tests_properties)
|
|
if(NOT AKGL_SUPPRESS_DEPENDENCY_TESTS)
|
|
_set_tests_properties(${ARGV})
|
|
endif()
|
|
endfunction()
|
|
|
|
set(JANSSON_WITHOUT_TESTS ON CACHE BOOL "Do not build vendored Jansson tests" FORCE)
|
|
set(JANSSON_EXAMPLES OFF CACHE BOOL "Do not build vendored Jansson examples" FORCE)
|
|
set(JANSSON_BUILD_DOCS OFF CACHE BOOL "Do not build vendored Jansson docs" FORCE)
|
|
|
|
add_subdirectory(deps/jansson EXCLUDE_FROM_ALL)
|
|
add_subdirectory(deps/libakerror EXCLUDE_FROM_ALL)
|
|
add_subdirectory(deps/libakstdlib EXCLUDE_FROM_ALL)
|
|
add_subdirectory(deps/SDL EXCLUDE_FROM_ALL)
|
|
add_subdirectory(deps/SDL_image EXCLUDE_FROM_ALL)
|
|
add_subdirectory(deps/SDL_mixer EXCLUDE_FROM_ALL)
|
|
add_subdirectory(deps/SDL_ttf EXCLUDE_FROM_ALL)
|
|
|
|
# Reserve error-name table entries for libakgl-specific status codes.
|
|
target_compile_definitions(akerror PUBLIC
|
|
AKERR_MAX_ERR_VALUE=256
|
|
)
|
|
|
|
set(AKGL_SUPPRESS_DEPENDENCY_TESTS FALSE)
|
|
|
|
else()
|
|
|
|
find_package(PkgConfig REQUIRED)
|
|
|
|
if(NOT TARGET SDL3::SDL3)
|
|
find_package(SDL3 REQUIRED)
|
|
endif()
|
|
if(NOT TARGET SDL3_image::SDL3_image)
|
|
find_package(SDL3_image REQUIRED)
|
|
endif()
|
|
if(NOT TARGET SDL3_mixer::SDL3_mixer)
|
|
find_package(SDL3_mixer REQUIRED)
|
|
endif()
|
|
if(NOT TARGET SDL3_ttf::SDL3_ttf)
|
|
find_package(SDL3_ttf REQUIRED)
|
|
endif()
|
|
if(NOT TARGET akerror::akerror)
|
|
find_package(akerror REQUIRED)
|
|
endif()
|
|
if(NOT TARGET akstdlib::akstdlib)
|
|
find_package(akstdlib REQUIRED)
|
|
endif()
|
|
if(NOT TARGET jansson::jansson)
|
|
find_package(jansson)
|
|
endif()
|
|
|
|
endif()
|
|
|
|
set(GAMECONTROLLERDB_H "include/akgl/SDL_GameControllerDB.h")
|
|
set(prefix ${CMAKE_INSTALL_PREFIX})
|
|
set(exec_prefix "\${prefix}")
|
|
set(libdir "\${exec_prefix}/lib")
|
|
set(includedir "\${prefix}/include")
|
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/akgl.pc.in ${CMAKE_CURRENT_BINARY_DIR}/akgl.pc @ONLY)
|
|
|
|
# Tests use both relative paths and SDL_GetBasePath(), so stage fixtures beside
|
|
# test executables in every out-of-tree build.
|
|
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/tests/assets"
|
|
DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
|
|
|
|
add_custom_command(
|
|
OUTPUT ${GAMECONTROLLERDB_H}
|
|
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/mkcontrollermappings.sh ${CMAKE_CURRENT_SOURCE_DIR}
|
|
COMMENT "Generating controller mappings ..."
|
|
)
|
|
|
|
# Add include directories
|
|
include_directories(${SDL3_INCLUDE_DIRS})
|
|
add_library(akgl SHARED
|
|
deps/semver/semver.c
|
|
src/actor.c
|
|
src/actor_state_string_names.c
|
|
src/text.c
|
|
src/assets.c
|
|
src/character.c
|
|
src/draw.c
|
|
src/game.c
|
|
src/controller.c
|
|
src/heap.c
|
|
src/json_helpers.c
|
|
src/registry.c
|
|
src/renderer.c
|
|
src/physics.c
|
|
src/sprite.c
|
|
src/staticstring.c
|
|
src/tilemap.c
|
|
src/util.c
|
|
${GAMECONTROLLERDB_H}
|
|
)
|
|
|
|
add_library(akgl::akgl ALIAS akgl)
|
|
|
|
add_executable(charviewer util/charviewer.c)
|
|
add_executable(test_semver_unit deps/semver/semver_unit.c)
|
|
|
|
# Every suite here is a standalone C program named tests/<name>.c, built as
|
|
# test_<name> and registered with CTest under <name>.
|
|
set(AKGL_TEST_SUITES
|
|
actor
|
|
bitmasks
|
|
character
|
|
controller
|
|
game
|
|
heap
|
|
json_helpers
|
|
physics
|
|
registry
|
|
sprite
|
|
staticstring
|
|
tilemap
|
|
util
|
|
)
|
|
|
|
foreach(suite IN LISTS AKGL_TEST_SUITES)
|
|
add_executable(test_${suite} tests/${suite}.c)
|
|
add_test(NAME ${suite} COMMAND test_${suite})
|
|
endforeach()
|
|
|
|
add_test(NAME semver_unit COMMAND test_semver_unit)
|
|
|
|
set_tests_properties(
|
|
${AKGL_TEST_SUITES} semver_unit
|
|
PROPERTIES WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tests" TIMEOUT 30
|
|
)
|
|
|
|
# Specify include directories for the library's headers (if applicable)
|
|
target_include_directories(akgl PUBLIC
|
|
include/
|
|
deps/semver/
|
|
)
|
|
|
|
if(AKGL_COVERAGE)
|
|
target_compile_options(akgl PRIVATE --coverage -O0 -g)
|
|
target_link_options(akgl PRIVATE --coverage)
|
|
|
|
set(AKGL_COVERAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/coverage")
|
|
file(MAKE_DIRECTORY "${AKGL_COVERAGE_DIR}")
|
|
|
|
add_test(
|
|
NAME coverage_reset
|
|
COMMAND ${GCOVR_EXECUTABLE}
|
|
--root "${CMAKE_CURRENT_SOURCE_DIR}"
|
|
--object-directory "${CMAKE_CURRENT_BINARY_DIR}"
|
|
--delete
|
|
)
|
|
set_tests_properties(coverage_reset PROPERTIES FIXTURES_SETUP akgl_coverage)
|
|
# Any suite missing from this list runs outside the fixture and has its
|
|
# counters discarded by coverage_reset.
|
|
set_tests_properties(
|
|
${AKGL_TEST_SUITES} semver_unit
|
|
PROPERTIES FIXTURES_REQUIRED akgl_coverage
|
|
)
|
|
|
|
add_test(
|
|
NAME coverage_report
|
|
COMMAND ${GCOVR_EXECUTABLE}
|
|
--root "${CMAKE_CURRENT_SOURCE_DIR}"
|
|
--object-directory "${CMAKE_CURRENT_BINARY_DIR}"
|
|
--filter "${CMAKE_CURRENT_SOURCE_DIR}/src/"
|
|
--xml-pretty
|
|
--xml "${AKGL_COVERAGE_DIR}/coverage.xml"
|
|
--html-details "${AKGL_COVERAGE_DIR}/index.html"
|
|
)
|
|
set_tests_properties(
|
|
coverage_report
|
|
PROPERTIES
|
|
FIXTURES_CLEANUP akgl_coverage
|
|
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
|
)
|
|
endif()
|
|
|
|
target_link_libraries(akgl
|
|
PUBLIC
|
|
SDL3::SDL3
|
|
SDL3_image::SDL3_image
|
|
SDL3_mixer::SDL3_mixer
|
|
SDL3_ttf::SDL3_ttf
|
|
akstdlib::akstdlib
|
|
akerror::akerror
|
|
jansson::jansson
|
|
)
|
|
|
|
foreach(suite IN LISTS AKGL_TEST_SUITES)
|
|
target_link_libraries(test_${suite} PRIVATE akstdlib::akstdlib akerror::akerror akgl SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer jansson::jansson -lm)
|
|
target_include_directories(test_${suite} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/tests")
|
|
endforeach()
|
|
|
|
target_link_libraries(charviewer PRIVATE akstdlib::akstdlib akerror::akerror akgl SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer jansson::jansson -lm)
|
|
|
|
# When the vendored SDL satellite libraries are built in-tree they land in per-
|
|
# project subdirectories that are not on the loader's default search path, so a
|
|
# freshly built test aborts before main() with "cannot open shared object file".
|
|
# Bake those directories into the build-tree RPATH. Installed builds resolve the
|
|
# same libraries through find_package and need no help.
|
|
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
|
set(AKGL_VENDORED_RPATH
|
|
"$<TARGET_FILE_DIR:SDL3::SDL3>"
|
|
"$<TARGET_FILE_DIR:SDL3_image::SDL3_image>"
|
|
"$<TARGET_FILE_DIR:SDL3_ttf::SDL3_ttf>"
|
|
"$<TARGET_FILE_DIR:SDL3_mixer::SDL3_mixer>"
|
|
"$<TARGET_FILE_DIR:akerror::akerror>"
|
|
"$<TARGET_FILE_DIR:akstdlib::akstdlib>"
|
|
)
|
|
foreach(suite IN LISTS AKGL_TEST_SUITES)
|
|
set_target_properties(test_${suite} PROPERTIES BUILD_RPATH "${AKGL_VENDORED_RPATH}")
|
|
endforeach()
|
|
set_target_properties(charviewer akgl PROPERTIES BUILD_RPATH "${AKGL_VENDORED_RPATH}")
|
|
|
|
# RPATH alone is not enough: LD_LIBRARY_PATH is searched first, so a developer
|
|
# who has previously run rebuild.sh has an installed libakgl.so ahead of the
|
|
# one under test. Prepend the build tree for the CTest run so the suite always
|
|
# exercises what was just compiled.
|
|
set(AKGL_TEST_LIBPATH
|
|
"${CMAKE_CURRENT_BINARY_DIR}"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/deps/SDL"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/deps/SDL_image"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/deps/SDL_ttf"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/deps/SDL_mixer"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/deps/libakerror"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/deps/libakstdlib"
|
|
)
|
|
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.22")
|
|
set(AKGL_TEST_ENV_MOD "")
|
|
foreach(dir IN LISTS AKGL_TEST_LIBPATH)
|
|
list(APPEND AKGL_TEST_ENV_MOD "LD_LIBRARY_PATH=path_list_prepend:${dir}")
|
|
endforeach()
|
|
set_tests_properties(
|
|
${AKGL_TEST_SUITES}
|
|
PROPERTIES ENVIRONMENT_MODIFICATION "${AKGL_TEST_ENV_MOD}"
|
|
)
|
|
else()
|
|
string(REPLACE ";" ":" AKGL_TEST_LIBPATH_JOINED "${AKGL_TEST_LIBPATH}")
|
|
set_tests_properties(
|
|
${AKGL_TEST_SUITES}
|
|
PROPERTIES ENVIRONMENT "LD_LIBRARY_PATH=${AKGL_TEST_LIBPATH_JOINED}:$ENV{LD_LIBRARY_PATH}"
|
|
)
|
|
endif()
|
|
endif()
|
|
|
|
# Mutation testing copies the repository to scratch space, applies one small
|
|
# source change at a time, and verifies that the passing tests detect it. The
|
|
# intentionally failing character test is excluded by the harness.
|
|
find_package(Python3 COMPONENTS Interpreter)
|
|
if(Python3_FOUND)
|
|
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
|
set(AKGL_MUTATION_TARGET mutation)
|
|
else()
|
|
set(AKGL_MUTATION_TARGET akgl_mutation)
|
|
endif()
|
|
add_custom_target(${AKGL_MUTATION_TARGET}
|
|
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 a scratch copy, expects tests to fail)"
|
|
)
|
|
endif()
|
|
|
|
set(main_lib_dest "lib/akgl-${MY_LIBRARY_VERSION}")
|
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akgl.pc DESTINATION "lib/pkgconfig/")
|
|
install(TARGETS akgl DESTINATION "lib/")
|
|
install(FILES "deps/semver/semver.h" DESTINATION "include/")
|
|
install(FILES "include/akgl/actor.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/types.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/text.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/assets.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/character.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/error.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/draw.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/game.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/controller.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/heap.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/iterator.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/json_helpers.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/renderer.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/physics.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/registry.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/sprite.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/staticstring.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/tilemap.h" DESTINATION "include/akgl/")
|
|
install(FILES "include/akgl/util.h" DESTINATION "include/akgl/")
|
|
install(FILES ${GAMECONTROLLERDB_H} DESTINATION "include/akgl/")
|