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