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>
87 lines
2.9 KiB
Bash
Executable File
87 lines
2.9 KiB
Bash
Executable File
#!/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
|