Files
RetroArch/tools/mingw_syntax_check.sh
T
LibretroAdmin c83441dfdd vulkan_common: two real C89 violations, and the pass that could not see them
gfx/common/vulkan_common.c has been failing the linux-c89 flags on
master: a declaration after a statement at the top of
vulkan_destroy_swapchain(), where the Win32 exclusive-mode release
sits above `unsigned i`, and two VkSurfaceFullScreenExclusive*
structs initialised from settings read earlier in the same function,
which C89 will not compute at load time. The declaration moves above
the #ifdef and the two structs are assigned rather than initialised;
no behaviour changes on any path.

Neither would have been caught by tools/mingw_syntax_check.sh even
when run over the whole tree, because the C89 pass did not define
HAVE_VULKAN: the file's settings members did not exist, so it failed
on those instead and the real violations never got that far. The
define joins that pass, which is what made both visible.

The decl gate added last commit then found six more files at the top
level using strlcpy through a transitive declaration - the core
option manager, the core updater list, both disk-control files, the
manual scanner and runahead - and they now include <compat/strl.h>
themselves.
2026-09-11 08:49:48 +02:00

157 lines
7.0 KiB
Bash
Executable File

#!/bin/sh
# Syntax-check every C translation unit a Win32 MSYS2 build compiles,
# with the defines that build actually uses, on a mingw cross compiler.
#
# Why this exists: a header edit checked by compiling only the file that
# was edited will pass while another TU that includes the same header
# fails. The include order differs per TU, so a typedef visible in one
# is absent in another (vulkan_win32.h behind an include guard set
# before VK_USE_PLATFORM_WIN32_KHR was defined, for instance). The only
# check that catches that is compiling all of them.
#
# Usage:
# tools/mingw_syntax_check.sh --consumers-of gfx/common/foo.h [...]
# compile every .c that directly includes any of the named headers.
# THIS is the check to run after editing a header.
# tools/mingw_syntax_check.sh file.c [...]
# just those files
# tools/mingw_syntax_check.sh
# every .c under gfx/ audio/ input/ menu/ and the top level
#
# Prints one line per failing TU and exits non-zero if any failed.
CC="${CC:-x86_64-w64-mingw32-gcc}"
command -v "$CC" >/dev/null 2>&1 || { echo "no $CC" >&2; exit 2; }
FLAGS="-fsyntax-only -std=gnu99 -I. -Ilibretro-common/include -Ideps \
-Ideps/stb -Igfx/include \
-DRARCH_INTERNAL -DHAVE_THREADS -DHAVE_CONFIGFILE -DHAVE_MENU \
-DHAVE_VULKAN -DHAVE_SLANG -DHAVE_SPIRV_CROSS -DHAVE_D3D11 -DHAVE_D3D12 \
-DHAVE_D3DKMT -DHAVE_DINPUT -DHAVE_XINPUT -DHAVE_WASAPI -DHAVE_XAUDIO \
-DHAVE_NETWORKING -DHAVE_CHEEVOS -DHAVE_RUNAHEAD -DHAVE_REWIND \
-DHAVE_OVERLAY -DHAVE_RGUI -DHAVE_XMB -DHAVE_OZONE -DHAVE_MATERIALUI \
-DHAVE_GFX_WIDGETS -DHAVE_SHADERPIPELINE -DHAVE_CG -DHAVE_GLSL \
-DHAVE_OPENGL -DHAVE_OPENGL_CORE -DHAVE_DSOUND -DHAVE_AUDIOMIXER \
-DHAVE_TRANSLATE -DHAVE_SCREENSHOTS -DHAVE_PATCH -DHAVE_BSV_MOVIE"
if [ "$1" = "--consumers-of" ]; then
shift
FILES=""
for h in "$@"; do
base=$(basename "$h")
# Linux-only test harnesses under */test/ are not Win32 / C89
# consumers (they have their own scripts under tools/).
FILES="$FILES $(grep -rl --include='*.c' "#include.*[\"/]$base\"" . \
| grep -v '^./deps/' | grep -v '/test/' | sed 's#^\./##')"
done
FILES=$(echo $FILES | tr ' ' '\n' | sort -u)
[ -z "$FILES" ] && { echo "no consumers found for: $*" >&2; exit 2; }
echo "checking $(echo "$FILES" | wc -l) consumers of: $*"
elif [ $# -gt 0 ]; then
FILES="$*"
else
FILES=$(find gfx audio input menu -name '*.c' \
-not -path '*/deps/*' -not -path '*/include/*' 2>/dev/null; \
ls *.c 2>/dev/null)
fi
# Pass 2: the linux-c89 CI job's flags, verbatim from the Makefile's
# C89_BUILD block, on the native compiler. -Werror=pedantic and
# -Werror=declaration-after-statement are what MSVC-era C89 compliance
# actually means here, and neither is implied by -std=gnu99 above. A
# file that passes pass 1 and fails this one is exactly what has broken
# the build before: a statement placed between declarations.
C89CC="${C89CC:-gcc}"
C89FLAGS="-fsyntax-only -std=c89 -ansi -pedantic -Werror=pedantic \
-Wno-long-long -Werror=declaration-after-statement -Wno-variadic-macros \
-D_GNU_SOURCE -I. -Ilibretro-common/include -Ideps -Ideps/stb -Igfx/include \
-DRARCH_INTERNAL -DHAVE_THREADS -DHAVE_CONFIGFILE -DHAVE_MENU \
-DHAVE_NETWORKING -DHAVE_CHEEVOS -DHAVE_RUNAHEAD -DHAVE_REWIND \
-DHAVE_AUDIOMIXER -DHAVE_OVERLAY -DHAVE_RGUI -DHAVE_XMB -DHAVE_OZONE \
-DHAVE_VULKAN"
# A Win32-only translation unit cannot be C89-checked with the host gcc:
# <windows.h> is not there, the pass dies on the include and the
# missing-header filter below forgives it - so ui_win32_companion.c was
# silently never checked, and MSVC 2005 found the C89 violations instead.
# Use the 32-bit mingw compiler for those when it is installed (the width
# MSVC 2005 builds, where a shift by 32 is undefined too).
C89CC_WIN32="${C89CC_WIN32:-i686-w64-mingw32-gcc}"
command -v "$C89CC_WIN32" >/dev/null 2>&1 || C89CC_WIN32=""
# Pass 0: the declaration a compiler here cannot miss.
#
# glibc 2.38 declares strlcpy and strlcat itself, so a .c that uses
# them without including <compat/strl.h> compiles clean on this box
# and on both passes below, then fails on MXE, clang and the webOS
# toolchain, where the only declaration is libretro-common's. That is
# how it reached master in modeline_edid.c. A grep is the only check
# that does not depend on what the host's headers happen to provide:
# a file that uses one of these must include the compat header
# itself, not lean on whatever a project header dragged in.
check_compat_include()
{
f="$1"
miss=""
if grep -qE '\b(strlcpy|strlcat|strlcpy_lit|strlcat_lit)[[:space:]]*\(' "$f" \
&& ! grep -q 'compat/strl\.h' "$f"; then
miss="compat/strl.h"
fi
if grep -qE '\bstrcasestr[[:space:]]*\(' "$f" \
&& ! grep -q 'compat/strcasestr\.h' "$f"; then
miss="$miss compat/strcasestr.h"
fi
[ -z "$miss" ] && return 0
echo "FAIL [decl] $f"
echo " uses a libretro-common string helper without including:$miss" \
| sed 's/^/ /'
return 1
}
fail=0; n=0
for f in $FILES; do
n=$((n+1))
check_compat_include "$f" || fail=1
# Only real errors, not warnings. A missing header named without a
# path (d3dkmthk.h: an optional platform header this box lacks) is
# forgiven; a missing header with a path component (../companion/x.h:
# a project header) is not - that once let a stale include of a
# deleted header through as "ok".
err=$($CC $FLAGS "$f" 2>&1 | grep -E ' error: ' \
| grep -vE 'error: [A-Za-z0-9_.-]+: No such file|error: [A-Za-z0-9_.-]+: file not found' | head -3)
if [ -n "$err" ]; then
echo "FAIL [win32] $f"; echo "$err" | sed 's/^/ /'; fail=1
fi
# Windows-only translation units cannot take pass 2 with the host
# gcc (<windows.h> is not there, and -ansi breaks those headers
# anyway). They still have to satisfy C89 - MSVC 2005 builds them -
# so use the 32-bit mingw compiler when it is installed: same width
# as that build, so a shift by 32 shows up too. Without it, say so
# rather than pass silently, which is how declarations after
# statements reached master in ui_win32_companion.c.
cc89="$C89CC"
case "$f" in
*win32*|*dinput*|*xinput*|*wasapi*|*xaudio*|*asio*|*dsound*|*d3d*|*dxgi*|*wgl*|*uwp*|*winraw*|*_w.c|*/w_*)
if [ -n "$C89CC_WIN32" ]; then
cc89="$C89CC_WIN32"
else
echo "skip [c89] $f (install gcc-mingw-w64-i686 to check it)"
continue
fi
;;
*)
if grep -q '#include <windows\.h>' "$f"; then
[ -n "$C89CC_WIN32" ] || { echo "skip [c89] $f (install gcc-mingw-w64-i686)"; continue; }
cc89="$C89CC_WIN32"
fi
;;
esac
err=$($cc89 $C89FLAGS -Wno-overlength-strings "$f" 2>&1 | grep -E ' error: ' \
| grep -vE 'error: [A-Za-z0-9_.-]+: No such file|error: [A-Za-z0-9_.-]+: file not found' | head -3)
if [ -n "$err" ]; then
echo "FAIL [c89] $f"; echo "$err" | sed 's/^/ /'; fail=1
fi
done
[ $fail = 0 ] && echo "ok: $n translation units clean (compat decls + win32 gnu99 + linux c89 pedantic)"
exit $fail