#!/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 [ ...] # # 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 [ ...]" >&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