libakerror 1.0.0 replaced the consumer-sized status-name array with a private registry and made status-code ownership explicit and enforced. AKERR_MAX_ERR_VALUE and __AKERR_ERROR_NAMES are gone, and the registry entry points raise akerr_ErrorContext * instead of returning int. See deps/libakerror/UPGRADING.md. The break was not only source-level. libakgl's codes sat at AKERR_LAST_ERRNO_VALUE + 18 through + 22, and 1.0.0 claimed exactly those five offsets for its own AKERR_STATUS_* registry codes, so every AKGL_ERR_* was aliasing a libakerror status. HANDLE(e, AKGL_ERR_LOGICINTERRUPT) at physics.c:222 would have swallowed a foreign-name refusal. - Move the band to AKERR_FIRST_CONSUMER_STATUS (256) as fixed offsets, so a libc that grows an errno cannot move the codes, and add AKGL_ERR_OWNER, AKGL_ERR_LIMIT and AKGL_ERR_COUNT to describe it. - Reserve the range and register the names through the owned entry points, PASS-ing each: these are AKERR_NOIGNORE, and the old akerr_name_for_status calls discarded failure silently. - Drop the AKERR_MAX_ERR_VALUE=256 compile definition. - Guard on AKERR_FIRST_CONSUMER_STATUS in include/akgl/error.h, which now includes <akerror.h> so the guard is reliable. The embedded build is fine, but the find_package path can pick up a stale installed header, and 1.0.0 has an soname, so that pairing is an ABI mismatch rather than a compile problem. Same guard libakstdlib already carries. Registration also moves out of akgl_heap_init into a new akgl_error_init in src/error.c. It was in the heap pool's initializer only because that was the first thing akgl_game_init called, and the upgrade turned five fire-and-forget name calls into a library-wide ownership claim that can fail. That placement was hiding a defect: game.c raises AKGL_ERR_SDL when SDL_CreateMutex fails, five lines before akgl_heap_init ran, so the earliest error path in the library was guaranteed to print "Unknown Error". akgl_error_init is now the first statement in akgl_game_init. Callers that drive subsystems directly must call akgl_error_init first; it is idempotent, so ordering it precisely is not required. The eleven test suites that relied on akgl_heap_init to name their statuses now call it explicitly, or their failure messages would have degraded to "Unknown Error". Add tests/error.c: assert every code reads back its registered name, that the name table and AKGL_ERR_COUNT agree, that a foreign owner is refused with AKERR_STATUS_NAME_FOREIGN and AKERR_STATUS_RANGE_OVERLAP, and that repeating the init is a no-op. That last one is a live constraint, not a triviality -- libakerror treats only an identical reservation as a repeat, so a subset or superset raises. 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)
|
|
|
|
# libakerror 1.0.0 sizes its own status-name registry; consumers no longer do.
|
|
# libakgl claims its status codes at runtime in akgl_heap_init() instead.
|
|
|
|
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/error.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
|
|
error
|
|
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/")
|