Files
libakgl/CMakeLists.txt
Andrew Kesterson 76ea04205c Stop returning past CLEANUP, and validate the arguments every sibling validates
Closes internal-consistency items 16 and 17.

Ten *_RETURN macros sat inside ATTEMPT blocks, which return past CLEANUP and
skip every release in it. The one that mattered was the success path of
akgl_get_json_tilemap_property: it leaked two of the string pool's 256 entries
on every lookup that *found* what it was asked for, and a map load does that
several times per layer. tests/tilemap.c now runs each of its three paths --
found, absent, wrong type -- twice the pool size and asserts the pool is where
it started. Against the old code that test does not merely fail, it segfaults,
which is Defects item 30 seen from the outside: pool exhaustion arriving as a
NULL strncpy rather than as AKGL_ERR_HEAP.

Two of the conversions needed more than swapping the macro. In
akgl_get_json_tilemap_property a plain break would have fallen through to the
"property not found" FAIL_RETURN after FINISH, reporting a miss for something
found, so the success path sets a flag. In akgl_collide_rectangles the eight
early exits are followed by `*collide = false;`, which would have overwritten
the hit that broke out; each corner test writes the flag itself, so that line
is gone rather than moved. The same function also released its scratch string
once per loop iteration while continuing to use it, so the slot was free while
still live -- one claim now covers the whole scan.

akgl_controller_default is the other behavioural one: its SUCCEED_RETURN was
the last statement in the ATTEMPT block, so the path that falls out of FINISH
reached the closing brace of a non-void function.

scripts/check_error_protocol.py keeps both rules enforced -- a *_RETURN inside
ATTEMPT, and a return out of a HANDLE block -- as the error_protocol test.
Neither produces a compiler diagnostic and neither fails a test run until the
pool it drains is empty, which is why both have already shipped once.

For item 17: the eight typed JSON accessors that validated their container and
then wrote through dest unconditionally now check key and dest as the two
string accessors always did; the null physics backend checks its actors like
the arcade one; and akgl_render_2d_frame_start, _frame_end and _shutdown check
self, which the first two read straight through. tests/renderer.c calls all
three with NULL, which segfaulted before.

25/25 pass, memcheck clean, reindent --check clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 07:33:35 -04:00

548 lines
22 KiB
CMake

cmake_minimum_required(VERSION 3.10)
# The single source of truth for the library version. It drives the generated
# include/akgl/version.h, the shared library's VERSION and SOVERSION, and the
# Version field in akgl.pc. Bump it here and nowhere else.
project(akgl VERSION 0.5.0 LANGUAGES C)
# Memory checking reuses the suites that already exist -- `ctest -T memcheck`
# runs every registered test under valgrind -- rather than adding programs of its
# own. The perf suites carry most of the weight there: they are the only things
# in the tree that load assets, draw a scene, and run a frame loop in one
# process, and tests/benchutil.h drops their iteration counts by three orders of
# magnitude when it finds itself under valgrind, which turns a benchmark into
# exactly the broad, once-through path coverage a leak check wants.
#
# These have to be set before include(CTest): that is what writes them into
# DartConfiguration.tcl, and a memcheck run reads them from there.
find_program(MEMORYCHECK_COMMAND valgrind)
# Set with FORCE, but only when empty. include(CTest) declares both of these as
# empty cache entries, so a build tree configured before this block existed has
# them already and a plain set(... CACHE ...) would be ignored -- the values
# would silently never reach DartConfiguration.tcl. Guarding on emptiness still
# leaves a deliberate -DMEMORYCHECK_SUPPRESSIONS_FILE=... alone.
if(NOT MEMORYCHECK_SUPPRESSIONS_FILE)
set(MEMORYCHECK_SUPPRESSIONS_FILE
"${CMAKE_CURRENT_SOURCE_DIR}/scripts/valgrind.supp"
CACHE FILEPATH "Suppressions for third-party findings the memcheck run cannot fix" FORCE)
endif()
# Definite losses only. "Still reachable" is every global SDL and FreeType keeps
# for the process lifetime and says nothing about libakgl; "possibly lost" is
# dominated by interior pointers into pools and thread stacks. Neither is worth
# the false positives.
if(NOT MEMORYCHECK_COMMAND_OPTIONS)
set(MEMORYCHECK_COMMAND_OPTIONS
"--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite --track-origins=yes --num-callers=25"
CACHE STRING "Options passed to the memory checker" FORCE)
endif()
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()
# Vendored projects own their test suites. Suppress their CTest registration so
# the suite that runs contains only the targets built here. The override is
# lifted again below, before this project registers its own tests.
#
# **Only when this project is top-level, and that guard is load-bearing.** CMake
# exposes an overridden command as `_name` and chains exactly one level deep: a
# second override rebinds `_add_test` to the *first* override and the builtin
# becomes unreachable to everybody, forever. So two projects in one tree cannot
# both shadow add_test() -- whoever goes second breaks, and an embedding
# consumer that shadows it first sees its own registrations recurse until CMake
# stops at depth 1000.
#
# An embedded libakgl therefore leaves the override alone and lets its consumer
# suppress what it does not want; the consumer has to have that machinery
# anyway, for libakerror and libakstdlib. libakstdlib guards its own shadow the
# same way and for the same reason.
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
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()
endif()
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 one vendored dependency, if nobody has already declared it and the
# submodule is actually checked out.
#
# A macro rather than a function on purpose: add_subdirectory() inside a
# function runs with that function's variable scope, so every cache-ish variable
# these projects set for their own subdirectories would be discarded on return.
macro(akgl_add_vendored_dependency target dir)
if(NOT TARGET ${target})
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${dir}/CMakeLists.txt")
add_subdirectory(${dir} EXCLUDE_FROM_ALL)
set(AKGL_VENDORED_DEPENDENCIES TRUE)
endif()
endif()
endmacro()
# Added on both paths, not just when this is the top-level project. Embedded
# with add_subdirectory(), the submodules under deps/ are sitting right there --
# the recursive clone the consumer just did put them there -- and refusing to
# configure until SDL3 is installed system-wide is a failure with its own answer
# three directories away. if(NOT TARGET ...) means a consumer that has already
# declared one of these wins; the EXISTS check means a checkout without
# submodules falls through to find_package below.
akgl_add_vendored_dependency(jansson::jansson deps/jansson)
akgl_add_vendored_dependency(akerror::akerror deps/libakerror)
akgl_add_vendored_dependency(akstdlib::akstdlib deps/libakstdlib)
akgl_add_vendored_dependency(SDL3::SDL3 deps/SDL)
akgl_add_vendored_dependency(SDL3_image::SDL3_image deps/SDL_image)
akgl_add_vendored_dependency(SDL3_mixer::SDL3_mixer deps/SDL_mixer)
akgl_add_vendored_dependency(SDL3_ttf::SDL3_ttf deps/SDL_ttf)
# 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)
# Anything the vendored block did not supply has to come from the system.
if(NOT (TARGET SDL3::SDL3 AND TARGET SDL3_image::SDL3_image AND
TARGET SDL3_mixer::SDL3_mixer AND TARGET SDL3_ttf::SDL3_ttf AND
TARGET akerror::akerror AND TARGET akstdlib::akstdlib AND
TARGET jansson::jansson))
# Only needed to locate installed copies; a fully vendored build does not
# require pkg-config to be present at all.
find_package(PkgConfig REQUIRED)
endif()
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()
# No version here: libakerror ships no akerrorConfigVersion.cmake, so asking
# for one makes find_package reject every install. The floor is enforced by
# the #error in include/akgl/error.h instead, which feature-tests
# AKERR_FIRST_CONSUMER_STATUS.
if(NOT TARGET akerror::akerror)
find_package(akerror REQUIRED)
endif()
# 0.2 rather than bare: libakstdlib 0.2.0 ships an akstdlibConfigVersion.cmake
# with SameMinorVersion compatibility, mirroring its soname, so this accepts
# any 0.2.x and refuses 0.3 and 1.0. Unversioned, this path would silently
# accept an ABI-incompatible libakstdlib.
if(NOT TARGET akstdlib::akstdlib)
find_package(akstdlib 0.2 REQUIRED)
endif()
if(NOT TARGET jansson::jansson)
find_package(jansson)
endif()
# Every header installed into include/akgl/, by base name. This list is the
# single source of truth for two things that must not drift apart: what gets
# installed, and what the `headers` suite proves is self-contained. Adding a
# header here is all a new one needs. SDL_GameControllerDB.h and the generated
# version.h are installed separately -- neither is an API header.
set(AKGL_PUBLIC_HEADERS
actor
assets
audio
character
controller
draw
error
game
heap
iterator
json_helpers
physics
registry
renderer
sprite
staticstring
text
tilemap
types
util
)
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)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/akgl/version.h.in
${CMAKE_CURRENT_BINARY_DIR}/include/akgl/version.h @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/audio.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
src/version.c
${GAMECONTROLLERDB_H}
)
# While the major version is 0 the ABI is not stable across minor releases, so
# the soname carries major.minor -- libakgl.so.0.1. A plain SOVERSION 0 would
# claim 0.1.0 and 0.2.0 are interchangeable, which is exactly the silent
# mispairing the soname is here to prevent. At 1.0.0 this becomes the major
# alone, matching libakerror.
if(PROJECT_VERSION_MAJOR EQUAL 0)
set(AKGL_SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
else()
set(AKGL_SOVERSION "${PROJECT_VERSION_MAJOR}")
endif()
set_target_properties(akgl PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${AKGL_SOVERSION}
)
add_library(akgl::akgl ALIAS akgl)
add_executable(charviewer util/charviewer.c)
add_executable(akgl_test_semver_unit deps/semver/semver_unit.c)
# Every suite here is a standalone C program named tests/<name>.c, built as
# akgl_test_<name> and registered with CTest under <name>.
set(AKGL_TEST_SUITES
actor
audio
bitmasks
character
controller
draw
error
game
headers
heap
json_helpers
physics
registry
renderer
sprite
staticstring
text
tilemap
util
version
)
# The performance suites are built and registered exactly like the unit suites,
# but they are benchmarks: they drive hot paths for millions of iterations, print
# a table of nanoseconds per operation, and fail only when a measurement exceeds
# a budget set at roughly ten times the recorded baseline. They carry the `perf`
# label, so `ctest -L perf` runs only them and `ctest -LE perf` leaves them out
# of an ordinary run, and they get a much longer timeout for obvious reasons.
#
# Set AKGL_BENCH_SCALE in the environment to change how long they run --
# `AKGL_BENCH_SCALE=0.1 ctest -L perf` for a tenth of the iterations. Below 1.0
# the budgets are measured and reported but not enforced, because a short run is
# a noisy one.
set(AKGL_PERF_SUITES
perf
perf_render
)
# The executables carry an akgl_ prefix but the CTest names do not: a vendored
# dependency is free to ship its own tests/version.c, and libakstdlib now does.
# Its target is created by add_subdirectory even though EXCLUDE_FROM_ALL keeps
# it from being built, so an unprefixed test_version here is a configure error.
foreach(suite IN LISTS AKGL_TEST_SUITES AKGL_PERF_SUITES)
add_executable(akgl_test_${suite} tests/${suite}.c)
add_test(NAME ${suite} COMMAND akgl_test_${suite})
endforeach()
add_test(NAME semver_unit COMMAND akgl_test_semver_unit)
# Not a C program, so it is not in AKGL_TEST_SUITES: it reads the built library's
# dynamic symbol table and every public header, and fails when a function with
# external linkage is declared nowhere. That is an ABI question rather than a
# behavioural one, and nineteen symbols had drifted out of the headers before
# anything was checking.
add_test(
NAME api_surface
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/scripts/check_api_surface.sh
$<TARGET_FILE:akgl>
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_BINARY_DIR}/include
)
set_tests_properties(api_surface PROPERTIES SKIP_RETURN_CODE 2)
# The two akerror control-flow rules whose failure mode is silent: a *_RETURN
# inside an ATTEMPT block, and a return out of a HANDLE block. Neither produces
# a compiler diagnostic and neither fails a test run until the pool it drains is
# empty, which is why both have already shipped.
#
# The mutation target below wants the same interpreter; find_package caches, so
# asking here as well costs nothing and keeps this block self-contained.
find_package(Python3 COMPONENTS Interpreter)
if(Python3_Interpreter_FOUND)
add_test(
NAME error_protocol
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/scripts/check_error_protocol.py
${CMAKE_CURRENT_SOURCE_DIR}/src
)
endif()
# One translation unit per public header, each including exactly that header and
# nothing before it. This is the only shape that proves a header is
# self-contained: a second #include in the same file proves nothing about the
# second, because by then the first has dragged its dependencies in. Generated
# rather than written by hand so the check cannot fall behind
# AKGL_PUBLIC_HEADERS -- the failure being guarded against is a header nobody
# remembered to cover.
set(AKGL_HEADER_SELFTEST_SOURCES "")
foreach(header IN LISTS AKGL_PUBLIC_HEADERS)
set(generated "${CMAKE_CURRENT_BINARY_DIR}/tests/header_${header}.c")
file(WRITE "${generated}"
"/* Generated by CMakeLists.txt from AKGL_PUBLIC_HEADERS. Do not edit. */\n\
#include <akgl/${header}.h>\n\
\n\
/* ISO C forbids an empty translation unit; the compile is the assertion. */\n\
int akgl_header_selftest_${header}(void);\n\
int akgl_header_selftest_${header}(void)\n\
{\n\
return 0;\n\
}\n")
list(APPEND AKGL_HEADER_SELFTEST_SOURCES "${generated}")
endforeach()
target_sources(akgl_test_headers PRIVATE ${AKGL_HEADER_SELFTEST_SOURCES})
# Hand-written rather than generated: it checks a header's behaviour under a
# caller-supplied override, which is not something the generated shape covers.
target_sources(akgl_test_headers PRIVATE tests/header_pool_override.c)
# TIMEOUT is generous because CTest applies the same property to `ctest -T
# memcheck`, where every suite runs under valgrind at something like twenty
# times its normal cost. The unit suites finish in well under a second each
# without it; the ceiling is there for the checked run, not the ordinary one.
set_tests_properties(
${AKGL_TEST_SUITES} semver_unit
PROPERTIES WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tests" TIMEOUT 300
)
set_tests_properties(
${AKGL_PERF_SUITES}
PROPERTIES
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tests"
TIMEOUT 900
LABELS perf
)
# Specify include directories for the library's headers (if applicable)
target_include_directories(akgl PUBLIC
include/
deps/semver/
# akgl/version.h is generated by configure_file, so it lives in the build tree
# rather than beside the headers it is included from.
${CMAKE_CURRENT_BINARY_DIR}/include/
)
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. The perf suites are deliberately
# outside it: an instrumented -O0 build measures gcov, not libakgl, and the
# lines they cover are covered by the unit suites anyway.
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 AKGL_PERF_SUITES)
target_link_libraries(akgl_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(akgl_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, so this keys on whether
# anything was actually vendored rather than on being the top-level project.
if(AKGL_VENDORED_DEPENDENCIES)
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 AKGL_PERF_SUITES)
set_target_properties(akgl_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} ${AKGL_PERF_SUITES}
PROPERTIES ENVIRONMENT_MODIFICATION "${AKGL_TEST_ENV_MOD}"
)
else()
string(REPLACE ";" ":" AKGL_TEST_LIBPATH_JOINED "${AKGL_TEST_LIBPATH}")
set_tests_properties(
${AKGL_TEST_SUITES} ${AKGL_PERF_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()
# Memory checking runs the whole registered suite under valgrind. The wrapper
# script exists because `ctest -T memcheck` records defects and still exits 0,
# which cannot gate anything; it also forces the headless drivers, so the vendor
# GPU stack is never loaded and never has to be suppressed.
if(MEMORYCHECK_COMMAND)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(AKGL_MEMCHECK_TARGET memcheck)
else()
set(AKGL_MEMCHECK_TARGET akgl_memcheck)
endif()
add_custom_target(${AKGL_MEMCHECK_TARGET}
COMMAND ${CMAKE_COMMAND} -E env
AKGL_BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/scripts/memcheck.sh
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
USES_TERMINAL
COMMENT "Running every test suite under valgrind (slow; the perf suites scale themselves down)"
)
endif()
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akgl.pc DESTINATION "lib/pkgconfig/")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/akgl/version.h DESTINATION "include/akgl/")
install(TARGETS akgl DESTINATION "lib/")
install(FILES "deps/semver/semver.h" DESTINATION "include/")
foreach(header IN LISTS AKGL_PUBLIC_HEADERS)
install(FILES "include/akgl/${header}.h" DESTINATION "include/akgl/")
endforeach()
install(FILES ${GAMECONTROLLERDB_H} DESTINATION "include/akgl/")