Files
libakgl/scripts/check_api_surface.sh

87 lines
2.9 KiB
Bash
Raw Normal View History

Give every exported function a declaration, and check that it stays that way Closes internal-consistency items 7 through 15. Nineteen non-static functions were in the ABI with no declaration anywhere, so no consumer could call them and any consumer could collide with them. The four gamepad_handle_* functions are the ones that mattered: controller.h declared akgl_controller_handle_button_down and three siblings that did not exist, so anything compiled against the header alone failed to link. The definitions carry the declared names now, which also closes Defects -> Known and still open item 10, and their documentation moved to the header. The rest are either declared under a "part of the internal API" block -- akgl_game_save_actors and akgl_game_load_versioncmp, which tests/game.c had to declare for itself, plus six tilemap loader helpers the untested-loader work wants to reach -- or static, which is what the four save iterators and load_objectnamemap should always have been. akgl_path_relative_from is deleted: declared nowhere, called from nowhere, never wrote its output, and leaked a pooled string on every call, so it closes Known and still open item 4 and item 40 by ceasing to exist. scripts/check_api_surface.sh keeps it closed. It reads the built library's dynamic symbol table and every public header with comments stripped, and fails on an exported akgl_* symbol that is declared nowhere. Stripping comments is the whole point -- four of these were mentioned in controller.h prose, which is how they went unnoticed. The pool-size ceilings are defined once, in heap.h, so the #ifndef override hook fires for the first time; actor.h, sprite.h and character.h were defining the same four unconditionally from headers heap.h includes above its own guard. tests/header_pool_override.c fails the compile if that regresses. Also here: (void) rather than () on the twelve no-argument entry points, AKERR_NOIGNORE only on declarations, static helpers with the akgl_ prefix dropped, and the six parameter-name mismatches. akgl_get_json_with_default had its two contexts swapped rather than merely misspelled -- the incoming one was `err` and its own was `e`, which is the name reserved for an incoming one. 24/24 pass, reindent --check clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:44:38 -04:00
#!/usr/bin/env bash
#
# Every function with external linkage must be declared in a header.
#
# A non-static function that appears in no header is still in the ABI but is
# unreachable by callers, and it collides with any consumer that happens to pick
# the same name. libakgl shipped nineteen of those. The rule is in AGENTS.md
# under "API Surface"; this is what enforces it.
#
# Usage: check_api_surface.sh <libakgl.so> <header-dir> [<header-dir> ...]
#
# Exits 0 when every exported akgl_* symbol is declared, 1 otherwise, and 2 when
# it cannot do its job -- a missing library or no nm.
set -u
if [ "$#" -lt 2 ]; then
echo "usage: $0 <libakgl.so> <header-dir> [<header-dir> ...]" >&2
exit 2
fi
LIBRARY="$1"
shift
if ! command -v nm >/dev/null 2>&1; then
echo "check_api_surface: nm is not available; skipping" >&2
exit 2
fi
if [ ! -f "${LIBRARY}" ]; then
echo "check_api_surface: no such library: ${LIBRARY}" >&2
exit 2
fi
DECLARATIONS="$(mktemp)"
trap 'rm -f "${DECLARATIONS}"' EXIT
# Comments are stripped first. Several of these names appear in prose -- the
# whole point of the check is that being *mentioned* in a header is not the same
# as being declared in one, and that distinction is exactly what went wrong with
# the gamepad_handle_* handlers.
for dir in "$@"; do
[ -d "${dir}" ] || continue
find "${dir}" -name '*.h' -print0 |
xargs -0 --no-run-if-empty cat |
sed -e 's://.*::' |
awk 'BEGIN { incomment = 0 }
{
line = $0
while ( 1 ) {
if ( incomment ) {
idx = index(line, "*/")
if ( idx == 0 ) { line = ""; break }
line = substr(line, idx + 2); incomment = 0
} else {
idx = index(line, "/*")
if ( idx == 0 ) { break }
printf "%s", substr(line, 1, idx - 1)
line = substr(line, idx + 2); incomment = 1
}
}
print line
}' >> "${DECLARATIONS}"
done
UNDECLARED=""
for symbol in $(nm -D --defined-only "${LIBRARY}" | awk '$2 == "T" { print $3 }' | grep '^akgl_' | sort); do
if ! grep -qE "(^|[^A-Za-z0-9_])${symbol}[^A-Za-z0-9_]" "${DECLARATIONS}"; then
UNDECLARED="${UNDECLARED} ${symbol}"
fi
done
if [ -n "${UNDECLARED}" ]; then
echo "check_api_surface: exported but declared in no header:" >&2
for symbol in ${UNDECLARED}; do
echo " ${symbol}" >&2
done
echo "" >&2
echo "Declare each one in the header for its subsystem, or make it static." >&2
echo "A function exposed only so tests can reach it goes under the existing" >&2
echo "\"part of the internal API\" comment block." >&2
exit 1
fi
echo "check_api_surface: every exported akgl_* symbol is declared"
exit 0