A shape is a convex volume positioned relative to an actor's origin. It lives on akgl_Character, so every goblin sharing a character shares one shape definition the way they already share speeds and the state-to-sprite map, and an actor may override it with akgl_Actor::shape_override. The flag is not redundant with an empty shape. "This actor deliberately has no collider" and "this actor has not been given one yet" are different states, and without somewhere to record the difference akgl_actor_set_character cannot tell whether it is allowed to overwrite what it finds. include/akgl/collision.h is a leaf: it names actors and tilemaps as incomplete struct pointers rather than including their headers, because both of those need akgl_CollisionShape by value and a cycle forms otherwise. The `headers` suite compiles it as the first include of a translation unit, so that property is enforced rather than merely intended -- which is why the tilemap types got struct tags two commits ago. The masks are asymmetric and the defaults are the load-bearing part: layermask ACTOR, collidemask STATIC. An actor given a shape and nothing else collides with map geometry and with no other actor. "Everything with a hitbox shoves everything else" would be a surprising default for a town full of scenery and a painful one to discover after the fact; opting in to actor-versus-actor is one bit, opting out of it would have been a hunt through every NPC a game spawns. Every shape gets a z depth, because the narrowphase behind this is three dimensional and answers with the *minimum* separating translation. A thin extrusion makes z the cheapest axis, and an actor pushed along z goes nowhere a player can see while remaining inside the floor -- a collision system reporting success while doing nothing. The ratio of 2 is the smallest for which the z overlap of any two shapes the setters build provably exceeds any planar penetration they can reach. The test for that asserts the inequality across a spread of sizes rather than asserting the constant is 2, so a change to the constant fails here rather than in a game. Two things about that test are worth recording, because the first version had both: - It could not fail. TEST_ASSERT expands to FAIL_BREAK, which reports by `break`ing, and the assertion was inside a nested `for` -- so the break left the loop rather than the ATTEMPT block and the failure evaporated. This is the hazard AGENTS.md documents for CATCH; it applies to the whole family. Verified by setting the ratio to 0.5 and watching the suite still pass, then fixing it and watching it report the 512x512 case by name. - tests/collision_arena.c had the same bug in a loop added in the previous commit, and it is fixed here too. Co-Authored-By: Claude Code <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
857 lines
37 KiB
CMake
857 lines
37 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.7.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)
|
|
|
|
# -Wall is always on for code this project owns. -Werror is not, and the
|
|
# distinction is deliberate.
|
|
#
|
|
# libakgl is consumed with add_subdirectory() -- akbasic does exactly that -- so
|
|
# a target-level -Werror makes every new compiler diagnostic a broken build for
|
|
# somebody else's project. The warning set is not even stable across build types
|
|
# here: an -O2 build reports ten -Wstringop-truncation warnings that -O0 and
|
|
# -fsyntax-only do not, because they need the middle end. A newer GCC or a
|
|
# switch to clang moves the line again.
|
|
#
|
|
# So the errors live in CI, where the compiler is pinned and a new warning is a
|
|
# task for the maintainer rather than an outage for a consumer.
|
|
option(AKGL_WERROR "Treat compiler warnings as errors. For CI; leave OFF when embedding." OFF)
|
|
set(AKGL_WARNING_FLAGS -Wall)
|
|
if(AKGL_WERROR)
|
|
list(APPEND AKGL_WARNING_FLAGS -Werror)
|
|
endif()
|
|
|
|
# -Wextra is deliberately not here. It adds 22 findings, 17 of which are
|
|
# inherent to the design rather than defects: 13 -Wunused-parameter, because a
|
|
# backend vtable entry or an SDL callback has to match a signature whether it
|
|
# reads every argument or not, and 4 -Wimplicit-fallthrough from libakerror's
|
|
# own PROCESS/HANDLE/HANDLE_GROUP, which fall through between case labels by
|
|
# design. Adopting it would mean disabling both permanently to gain 5
|
|
# -Wsign-compare. See deps/libakerror/TODO.md item 8.
|
|
|
|
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 sizes its own status-name registry; consumers no longer do. libakgl
|
|
# claims its status codes at runtime in akgl_heap_init() instead.
|
|
#
|
|
# The floor is 2.0.1, and it is a hard one. 2.0.0 moved __akerr_last_ignored to
|
|
# thread-local storage and made akerr_next_error() return a context that already
|
|
# holds its reference -- both of which expand at *libakgl's* call sites through
|
|
# IGNORE and the FAIL macros, and at a *consumer's* too, because akerror.h is
|
|
# part of libakgl's public interface. A tree that mixes headers and libraries
|
|
# across that line double-counts every reference and never returns a pool slot.
|
|
# The soname moved to libakerror.so.2 to stop it happening by accident; the
|
|
# #error in include/akgl/error.h catches a stale header in an install tree.
|
|
|
|
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 and AKERR_EXIT_STATUS_UNREPRESENTABLE -- the
|
|
# latter arriving in 2.0.1, which is the version libakgl needs.
|
|
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
|
|
collision
|
|
collision_arena
|
|
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}")
|
|
|
|
# Regenerating the controller database is a deliberate act, not part of a build.
|
|
#
|
|
# This used to be an add_custom_command whose OUTPUT was the *relative* path
|
|
# "include/akgl/SDL_GameControllerDB.h", which CMake resolves against the binary
|
|
# directory while the script writes to the source directory. The declared output
|
|
# therefore never appeared, the command was permanently out of date, and every
|
|
# build re-ran it -- so every build needed network access and left the tracked
|
|
# header dirty with a fresh $(date) stamp.
|
|
#
|
|
# `cmake --build build --target controllerdb` when newer mappings are actually
|
|
# wanted. The result is its own commit, per AGENTS.md.
|
|
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
|
set(AKGL_CONTROLLERDB_TARGET controllerdb)
|
|
else()
|
|
set(AKGL_CONTROLLERDB_TARGET akgl_controllerdb)
|
|
endif()
|
|
add_custom_target(${AKGL_CONTROLLERDB_TARGET}
|
|
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/mkcontrollermappings.sh ${CMAKE_CURRENT_SOURCE_DIR}
|
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
|
USES_TERMINAL
|
|
COMMENT "Fetching controller mappings and rewriting ${GAMECONTROLLERDB_H} ..."
|
|
)
|
|
|
|
# libccd supplies the collision narrowphase (GJK, EPA and MPR).
|
|
#
|
|
# Its sources are listed here rather than add_subdirectory()'d. Four things in
|
|
# deps/libccd/CMakeLists.txt make it unusable as a subproject, and none of them
|
|
# is ours to fix in a submodule:
|
|
#
|
|
# - project(libccd) declares no LANGUAGES, so it enables CXX and makes a C++
|
|
# toolchain a requirement of every build of libakgl. CI installs gcc.
|
|
# - it declares option(BUILD_SHARED_LIBS ... ON), a global cache variable, and
|
|
# calls include(CTest), which would register its own suites into ours and
|
|
# build the LGPL-licensed CU framework under src/testsuites/.
|
|
# - install(FILES "${CMAKE_BINARY_DIR}/ccd.pc") reads the *top level* binary
|
|
# dir while configure_file writes CMAKE_CURRENT_BINARY_DIR. Those are the
|
|
# same directory standalone and different ones embedded, so `cmake --install`
|
|
# fails on a missing file.
|
|
# - cmake_minimum_required(VERSION 2.8.11) warns on CMake 3.28 and is a hard
|
|
# error on CMake 4.
|
|
#
|
|
# Compiling the sources directly, the way deps/semver/semver.c already is, sidesteps
|
|
# all four and never configures the LGPL testsuites at all.
|
|
set(CCD_SINGLE 1)
|
|
configure_file(
|
|
${CMAKE_CURRENT_SOURCE_DIR}/deps/libccd/src/ccd/config.h.cmake.in
|
|
${CMAKE_CURRENT_BINARY_DIR}/ccd/config.h)
|
|
|
|
set(AKGL_CCD_SOURCES
|
|
deps/libccd/src/ccd.c
|
|
deps/libccd/src/mpr.c
|
|
deps/libccd/src/polytope.c
|
|
deps/libccd/src/support.c
|
|
deps/libccd/src/vec3.c)
|
|
|
|
# Add include directories
|
|
include_directories(${SDL3_INCLUDE_DIRS})
|
|
add_library(akgl SHARED
|
|
deps/semver/semver.c
|
|
${AKGL_CCD_SOURCES}
|
|
src/actor.c
|
|
src/collision.c
|
|
src/collision_arena.c
|
|
src/collision_shape.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
|
|
)
|
|
|
|
# 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}
|
|
)
|
|
|
|
target_compile_options(akgl PRIVATE ${AKGL_WARNING_FLAGS})
|
|
# deps/semver/semver.c is vendored and listed directly in add_library(), so the
|
|
# target's PRIVATE options reach it. Exempt it: a future semver update must not
|
|
# be able to fail this build. (PRIVATE options do not reach SDL, jansson,
|
|
# akerror or akstdlib -- those are separate targets.)
|
|
set_source_files_properties(deps/semver/semver.c PROPERTIES COMPILE_OPTIONS "-w")
|
|
|
|
# libccd is vendored on the same terms as semver: -w so a future bump cannot fail
|
|
# this build on a warning we do not own.
|
|
#
|
|
# src/ccd_arena_shim.h is what keeps the "libakgl does not call malloc at
|
|
# runtime" promise true with the whole of libccd compiled in. Injected with
|
|
# -include, it redirects both halves of libccd's allocation -- the
|
|
# __CCD_ALLOC_MEMORY macro every CCD_ALLOC* expands to, and the four bare free()
|
|
# calls -- at the arena in src/collision_arena.c. It applies to these five files
|
|
# and nothing else.
|
|
#
|
|
# The redirect lives in that header rather than in COMPILE_DEFINITIONS because
|
|
# CMake cannot carry a function-like macro through a -D: it dropped it silently,
|
|
# and libccd went on calling the C library while free() became a no-op. Do not
|
|
# move it back.
|
|
#
|
|
# CCD_STATIC_DEFINE plus -fvisibility=hidden keeps every ccd* symbol out of
|
|
# libakgl.so's dynamic table. That is not tidiness: AGENTS.md records a shipped
|
|
# defect where an exported `renderer` in this library was preempted by a
|
|
# same-named symbol in a test executable, and a game linking both libakgl and a
|
|
# system libccd would hit exactly that.
|
|
set_source_files_properties(${AKGL_CCD_SOURCES} PROPERTIES
|
|
COMPILE_OPTIONS "-w;-fvisibility=hidden;-include;${CMAKE_CURRENT_SOURCE_DIR}/src/ccd_arena_shim.h"
|
|
COMPILE_DEFINITIONS "CCD_STATIC_DEFINE")
|
|
|
|
# PRIVATE, so that ccd/*.h never reaches a consumer's include path. The `headers`
|
|
# suite compiles each public header as the first include of a translation unit
|
|
# with only include/ available, so a public header that pulled in <ccd/ccd.h>
|
|
# would fail there immediately.
|
|
target_include_directories(akgl PRIVATE
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/deps/libccd/src"
|
|
"${CMAKE_CURRENT_BINARY_DIR}")
|
|
|
|
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
|
|
collision
|
|
collision_arena
|
|
controller
|
|
draw
|
|
error
|
|
game
|
|
headers
|
|
heap
|
|
json_helpers
|
|
physics
|
|
physics_sim
|
|
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}")
|
|
|
|
# Both gcovr invocations take the build tree as an explicit positional search
|
|
# path, and neither passes --object-directory.
|
|
#
|
|
# gcovr looks for .gcda/.gcno under its search paths, and with none given it
|
|
# searches --root -- the source directory, which is where developers keep
|
|
# their build trees. --object-directory does *not* narrow that: per gcovr's
|
|
# own help it only identifies "the path between gcda files and the directory
|
|
# where the compiler was originally run". So every instrumented tree left
|
|
# inside the source directory was folded into the report alongside the one
|
|
# being measured, and with two trees describing different line numbers for the
|
|
# same function, coverage_reset failed before any test ran:
|
|
#
|
|
# AssertionError: Got function akgl_game_lowfps on multiple lines: 45, 46
|
|
#
|
|
# The `build*/` entry in .gitignore hides those trees from git status, which
|
|
# makes the state easy to get into and hard to notice.
|
|
|
|
add_test(
|
|
NAME coverage_reset
|
|
COMMAND ${GCOVR_EXECUTABLE}
|
|
--root "${CMAKE_CURRENT_SOURCE_DIR}"
|
|
--delete
|
|
"${CMAKE_CURRENT_BINARY_DIR}"
|
|
)
|
|
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}"
|
|
--filter "${CMAKE_CURRENT_SOURCE_DIR}/src/"
|
|
--xml-pretty
|
|
--xml "${AKGL_COVERAGE_DIR}/coverage.xml"
|
|
--html-details "${AKGL_COVERAGE_DIR}/index.html"
|
|
"${CMAKE_CURRENT_BINARY_DIR}"
|
|
)
|
|
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")
|
|
target_compile_options(akgl_test_${suite} PRIVATE ${AKGL_WARNING_FLAGS})
|
|
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)
|
|
target_compile_options(charviewer PRIVATE ${AKGL_WARNING_FLAGS})
|
|
|
|
# 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()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# The documentation's own examples.
|
|
#
|
|
# docs/ is a manual full of C snippets, asset files and shell commands, and the
|
|
# README this project started with proves what happens to samples nothing
|
|
# executes: unbalanced PASS() calls, a stray `9` inside a bitmask expression, an
|
|
# `int screenwidth = NULL`, and the exact strncpy call AGENTS.md forbids. Two
|
|
# prose claims in the headers were false against src/ as well.
|
|
#
|
|
# Documentation goes stale because the *code* moved, not because somebody edited
|
|
# a chapter, so this is an ordinary test case rather than something a docs-only
|
|
# CI job runs behind a path filter: it fails when the library changes under the
|
|
# chapter, which is exactly the case a path filter would miss.
|
|
#
|
|
# tests/docs_examples.sh reads the fence info strings; docs/MAINTENANCE.md
|
|
# documents them.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# The snippets compile against the real include path, which is transitive
|
|
# through akerror, akstdlib, SDL3, SDL3_image and jansson. Writing it out from
|
|
# the target's own INCLUDE_DIRECTORIES keeps one source of truth: a hardcoded -I
|
|
# list in the script would rot exactly the way the documentation does.
|
|
#
|
|
# INTERFACE_INCLUDE_DIRECTORIES of the linked targets as well as libakgl's own,
|
|
# because akerror.h and akstdlib.h are part of libakgl's public interface and
|
|
# neither lives under include/akgl. akgl's own INCLUDE_DIRECTORIES does not
|
|
# carry them -- they arrive at a consumer through target_link_libraries -- so
|
|
# taking only that property leaves a snippet unable to find <akerror.h>.
|
|
set(AKGL_DOCS_DEPENDENCY_TARGETS
|
|
SDL3::SDL3
|
|
SDL3_image::SDL3_image
|
|
SDL3_mixer::SDL3_mixer
|
|
SDL3_ttf::SDL3_ttf
|
|
akstdlib::akstdlib
|
|
akerror::akerror
|
|
jansson::jansson
|
|
)
|
|
|
|
set(AKGL_DOCS_CFLAGS_FILE "${CMAKE_CURRENT_BINARY_DIR}/docs_cflags.txt")
|
|
# Each entry is wrapped in $<$<BOOL:...>:...>, because a target whose property
|
|
# is empty would otherwise contribute a bare `-I` line that swallows the next
|
|
# flag. $<FILTER> and $<REMOVE_DUPLICATES> would say it more directly and both
|
|
# need CMake 3.15; this file declares 3.10 and means it.
|
|
set(AKGL_DOCS_CFLAGS "$<$<BOOL:$<TARGET_PROPERTY:akgl,INCLUDE_DIRECTORIES>>:-I$<JOIN:$<TARGET_PROPERTY:akgl,INCLUDE_DIRECTORIES>,\n-I>\n>")
|
|
foreach(_docstarget IN LISTS AKGL_DOCS_DEPENDENCY_TARGETS)
|
|
string(APPEND AKGL_DOCS_CFLAGS
|
|
"$<$<BOOL:$<TARGET_PROPERTY:${_docstarget},INTERFACE_INCLUDE_DIRECTORIES>>:-I$<JOIN:$<TARGET_PROPERTY:${_docstarget},INTERFACE_INCLUDE_DIRECTORIES>,\n-I>\n>")
|
|
endforeach()
|
|
file(GENERATE
|
|
OUTPUT "${AKGL_DOCS_CFLAGS_FILE}"
|
|
CONTENT "${AKGL_DOCS_CFLAGS}"
|
|
)
|
|
|
|
# The link line for the `c run=` and `c screenshot=` blocks. Full library paths
|
|
# plus an rpath entry per directory: a vendored build leaves the SDL satellite
|
|
# libraries in per-project subdirectories that are not on the loader's default
|
|
# search path, which is the same problem AKGL_VENDORED_RPATH solves for the test
|
|
# executables.
|
|
set(AKGL_DOCS_LDFLAGS_FILE "${CMAKE_CURRENT_BINARY_DIR}/docs_ldflags.txt")
|
|
set(AKGL_DOCS_LDFLAGS "")
|
|
foreach(_docstarget akgl ${AKGL_DOCS_DEPENDENCY_TARGETS})
|
|
string(APPEND AKGL_DOCS_LDFLAGS "$<TARGET_LINKER_FILE:${_docstarget}>\n")
|
|
string(APPEND AKGL_DOCS_LDFLAGS "-Wl,-rpath,$<TARGET_FILE_DIR:${_docstarget}>\n")
|
|
endforeach()
|
|
string(APPEND AKGL_DOCS_LDFLAGS "-lm\n")
|
|
file(GENERATE
|
|
OUTPUT "${AKGL_DOCS_LDFLAGS_FILE}"
|
|
CONTENT "${AKGL_DOCS_LDFLAGS}"
|
|
)
|
|
|
|
# The json kind= validator: a host that brings libakgl up headless and hands the
|
|
# block to the loader that really reads that format. Not a test of its own -- it
|
|
# answers a question about a document rather than about the library -- and not
|
|
# instrumented, so a documentation run cannot flatter the coverage figure.
|
|
add_executable(akgl_docs_checkjson tools/docs_checkjson.c)
|
|
target_compile_options(akgl_docs_checkjson PRIVATE ${AKGL_WARNING_FLAGS})
|
|
target_link_libraries(akgl_docs_checkjson
|
|
PRIVATE akstdlib::akstdlib akerror::akerror akgl SDL3::SDL3 jansson::jansson -lm)
|
|
if(AKGL_VENDORED_DEPENDENCIES)
|
|
set_target_properties(akgl_docs_checkjson PROPERTIES BUILD_RPATH "${AKGL_VENDORED_RPATH}")
|
|
endif()
|
|
|
|
# tools/docs_screenshot.c is deliberately not a target here. It supplies main()
|
|
# for a program whose other translation unit is a block of markdown, so it is
|
|
# compiled by tools/docs_screenshots.sh together with the listing it is hosting;
|
|
# there is nothing for `all` to build.
|
|
|
|
add_test(
|
|
NAME docs_examples
|
|
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/docs_examples.sh
|
|
--root "${CMAKE_CURRENT_SOURCE_DIR}"
|
|
--cflags-file "${AKGL_DOCS_CFLAGS_FILE}"
|
|
--ldflags-file "${AKGL_DOCS_LDFLAGS_FILE}"
|
|
--checkjson $<TARGET_FILE:akgl_docs_checkjson>
|
|
)
|
|
# Every argument is spelled out rather than assembled from a generator
|
|
# expression: an expression that evaluates to nothing still contributes an
|
|
# *empty argument*, which the script reads as the first filename. akbasic's
|
|
# suite passed having checked no documents at all that way, and was caught only
|
|
# because it reports what it ran.
|
|
set_tests_properties(docs_examples PROPERTIES
|
|
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
|
TIMEOUT 900
|
|
ENVIRONMENT "SDL_VIDEODRIVER=dummy;SDL_AUDIODRIVER=dummy;SDL_RENDER_DRIVER=software"
|
|
)
|
|
|
|
# Every figure in docs/ re-rendered and byte-compared against the tracked copy.
|
|
# docs_examples only asks whether the file *exists*, which catches a figure that
|
|
# was never generated and not one that stopped being of the code beside it --
|
|
# and that second failure is the one this whole arrangement is against.
|
|
#
|
|
# **A byte comparison of a rendered PNG is a deliberate bet**: that the dummy
|
|
# video driver and the software renderer are reproducible run to run and build to
|
|
# build. What is untested is an SDL upgrade that shifts one pixel of a diagonal,
|
|
# and the answer to that is to regenerate the figures in the same commit as the
|
|
# bump -- not to weaken this to a size check, which would pass for every wrong
|
|
# picture that happened to be 320x240.
|
|
add_test(
|
|
NAME docs_screenshots
|
|
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tools/docs_screenshots.sh
|
|
--root "${CMAKE_CURRENT_SOURCE_DIR}"
|
|
--cflags-file "${AKGL_DOCS_CFLAGS_FILE}"
|
|
--ldflags-file "${AKGL_DOCS_LDFLAGS_FILE}"
|
|
--check
|
|
)
|
|
set_tests_properties(docs_screenshots PROPERTIES
|
|
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
|
TIMEOUT 900
|
|
ENVIRONMENT "SDL_VIDEODRIVER=dummy;SDL_AUDIODRIVER=dummy;SDL_RENDER_DRIVER=software"
|
|
)
|
|
|
|
# Regenerating the figures is a deliberate act, never part of a build: the PNGs
|
|
# are tracked, and a rebuild that silently rewrote them would put a binary diff
|
|
# in front of anybody who happened to run `make`. Same contract as the
|
|
# controllerdb target above, and for the same reason.
|
|
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
|
set(AKGL_DOCS_SCREENSHOTS_TARGET docs_screenshots)
|
|
else()
|
|
set(AKGL_DOCS_SCREENSHOTS_TARGET akgl_docs_screenshots)
|
|
endif()
|
|
add_custom_target(${AKGL_DOCS_SCREENSHOTS_TARGET}
|
|
COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tools/docs_screenshots.sh"
|
|
--root "${CMAKE_CURRENT_SOURCE_DIR}"
|
|
--cflags-file "${AKGL_DOCS_CFLAGS_FILE}"
|
|
--ldflags-file "${AKGL_DOCS_LDFLAGS_FILE}"
|
|
DEPENDS akgl
|
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
|
COMMENT "Regenerating the documentation figures in docs/images"
|
|
VERBATIM
|
|
)
|
|
|
|
# The two tutorial games. Guarded because they are written by a later pass and
|
|
# an absent directory must not break the configure -- and guarded again inside
|
|
# examples/CMakeLists.txt, one game at a time, for the same reason.
|
|
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/examples/CMakeLists.txt")
|
|
add_subdirectory(examples)
|
|
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/")
|