ci(windows): fix mingw build round 2

adds chill to the pruning step in the mingw workflow
Also mirrors the changes to the nightly workflow
This commit is contained in:
cheezwiz7899
2026-04-06 04:27:11 +10:00
parent 14611d241e
commit 048fafd2c9
2 changed files with 153 additions and 24 deletions
+61 -22
View File
@@ -180,8 +180,7 @@ jobs:
working-directory: ./citron
run: |
# vcpkg's DLL directory is not on the shell PATH, so CMake's deploy step
# and ldd both miss these DLLs entirely. Copy them explicitly into build/bin
# so the Package step picks them up alongside Qt/MSYS2 runtime DLLs.
# and ldd both miss these DLLs entirely.
VCPKG_BIN="./build/vcpkg_installed/x64-mingw-dynamic/bin"
BIN_DIR="./build/bin"
if [ -d "$VCPKG_BIN" ]; then
@@ -193,37 +192,78 @@ jobs:
- name: Prune unused DLLs from build/bin (MinGW)
working-directory: ./citron
run: |
# CopyMinGWDeps.cmake (citron's own cmake script) recursively resolves all
# transitive DLL dependencies of citron.exe through the UCRT64 bin directory.
# Since ffmpeg is installed system-wide in UCRT64, this pulls in ffmpeg's
# entire ~80-DLL codec dependency tree (libaom, libdav1d, libx264, libx265,
# librsvg, libgnutls, libcairo, libpango, libglib, and ~70 more).
# These codec DLLs are not directly imported by citron — ffmpeg uses them via
# its internal codec registration, which runs fine without them (ffmpeg simply
# skips unavailable codecs). Citron only needs the six core ffmpeg DLLs.
# CopyMinGWDeps.cmake pulls in the full transitive closure of citron.exe
# through /ucrt64/bin, including many DLLs that are never actually loaded.
#
# We keep only DLLs matching an explicit keeplist. Everything else is removed.
# We compute the true required set using a full recursive objdump closure:
# starting from the citron binaries, we follow every DLL import reference
# and collect any DLL that is present in build/bin. We repeat until no
# new DLLs are added. Anything not reached by this walk is deleted.
#
# This is correct by construction: if Windows would load a DLL at startup
# (directly or transitively through another DLL we ship), it will be in
# the closure. If it would not be loaded, it won't be. No hardcoded lists.
BIN_DIR="./build/bin"
echo "=== DLLs before pruning: $(find "$BIN_DIR" -maxdepth 1 -name "*.dll" | wc -l) ==="
# Patterns matched against the DLL basename (case-insensitive prefix match).
# Anything NOT matching this list is deleted from build/bin.
KEEP_PATTERN='^(citron|legacy\.|avcodec-|avfilter-|avformat-|avutil-|swresample-|swscale-|Qt6|SDL2|OpenAL32|vulkan-1\.|libcrypto|libssl|libboost|libzlib|zlib1\.|liblz4\.|libzstd\.|libbz2|liblzma|libopus|libfmt\.|libgcc_s_seh|libstdc\+\+|libwinpthread|libfreetype|libharfbuzz|libpcre2|libpng|libiconv|libicudt|libicuin|libicuuc|libmd4c|libdouble-conversion|libb2-|libgraphite2|libbrotli)'
# Build a lookup of lowercase dll name -> path for every DLL in bin/
declare -A bin_dlls
while IFS= read -r dll_path; do
dll_name=$(basename "$dll_path" | tr '[:upper:]' '[:lower:]')
bin_dlls["$dll_name"]="$dll_path"
done < <(find "$BIN_DIR" -maxdepth 1 -name "*.dll")
# Closure: set of lowercase dll names known to be required
declare -A closure
# Queue: dll names to process
declare -a queue
# Seed with the citron executables
for exe in "$BIN_DIR"/citron.exe "$BIN_DIR"/citron-cmd.exe "$BIN_DIR"/citron-room.exe; do
[[ -f "$exe" ]] || continue
while IFS= read -r dep; do
dep_lower=$(echo "$dep" | tr '[:upper:]' '[:lower:]')
if [[ -n "${bin_dlls[$dep_lower]+_}" && -z "${closure[$dep_lower]+_}" ]]; then
closure["$dep_lower"]=1
queue+=("$dep_lower")
fi
done < <(objdump -p "$exe" 2>/dev/null | awk '/DLL Name:/{print $3}')
done
# BFS: expand through each newly discovered DLL in bin/
while [[ ${#queue[@]} -gt 0 ]]; do
current="${queue[0]}"
queue=("${queue[@]:1}")
dll_path="${bin_dlls[$current]}"
while IFS= read -r dep; do
dep_lower=$(echo "$dep" | tr '[:upper:]' '[:lower:]')
if [[ -n "${bin_dlls[$dep_lower]+_}" && -z "${closure[$dep_lower]+_}" ]]; then
closure["$dep_lower"]=1
queue+=("$dep_lower")
fi
done < <(objdump -p "$dll_path" 2>/dev/null | awk '/DLL Name:/{print $3}')
done
echo "Closure size: ${#closure[@]} DLLs required"
# Remove everything in bin/ that is not in the closure
removed=0
while IFS= read -r dll; do
dll_name=$(basename "$dll")
if ! echo "$dll_name" | grep -qiE "$KEEP_PATTERN"; then
echo " Pruning: $dll_name"
rm -f "$dll"
kept=0
while IFS= read -r dll_path; do
dll_lower=$(basename "$dll_path" | tr '[:upper:]' '[:lower:]')
if [[ -z "${closure[$dll_lower]+_}" ]]; then
echo " Pruning: $(basename "$dll_path")"
rm -f "$dll_path"
removed=$((removed + 1))
else
kept=$((kept + 1))
fi
done < <(find "$BIN_DIR" -maxdepth 1 -name "*.dll")
echo "=== Pruned $removed DLL(s) ==="
echo "=== DLLs remaining: $(find "$BIN_DIR" -maxdepth 1 -name "*.dll" | wc -l) ==="
echo "=== Pruned $removed, kept $kept ==="
echo "=== Final DLL list ==="
find "$BIN_DIR" -maxdepth 1 -name "*.dll" | sort | xargs -I{} basename {}
- name: Package Windows Build (MinGW)
@@ -232,7 +272,6 @@ jobs:
mkdir dist
mkdir temp_package
# Copy the built binaries (MinGW outputs to bin/ directly, not bin/Release/)
$binDir = ".\citron\build\bin"
if (Test-Path "$binDir\Release") { $binDir = "$binDir\Release" }
+92 -2
View File
@@ -719,7 +719,7 @@ jobs:
id: cache-build
with:
path: ./citron/build
key: mingw-build-${{ runner.os }}-${{ hashFiles('citron/CMakeLists.txt') }}
key: mingw-build-${{ runner.os }}-${{ hashFiles('citron/CMakeLists.txt') }}-${{ hashFiles('.github/workflows/build_nightly.yml') }}
restore-keys: |
mingw-build-${{ runner.os }}-
@@ -731,6 +731,10 @@ jobs:
VERSION=$(git rev-parse --short HEAD)
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
- name: Clear stale CMake cache
working-directory: ./citron
run: rm -f build/CMakeCache.txt
- name: Configure CMake
working-directory: ./citron
run: |
@@ -751,7 +755,7 @@ jobs:
-DENABLE_OPENSSL=ON \
-DENABLE_CUBEB=OFF \
-DENABLE_OPENAL=ON \
-DENABLE_LIBUSB=ON \
-DENABLE_LIBUSB=OFF \
-DUSE_DISCORD_PRESENCE=OFF \
-DCITRON_USE_AUTO_UPDATER=OFF \
-DCITRON_USE_PRECOMPILED_HEADERS=ON
@@ -760,6 +764,92 @@ jobs:
working-directory: ./citron
run: cmake --build build --config Release -j$(nproc)
- name: Copy vcpkg runtime DLLs to bin
working-directory: ./citron
run: |
# vcpkg's DLL directory is not on the shell PATH, so CMake's deploy step
# and ldd both miss these DLLs entirely.
VCPKG_BIN="./build/vcpkg_installed/x64-mingw-dynamic/bin"
BIN_DIR="./build/bin"
if [ -d "$VCPKG_BIN" ]; then
find "$VCPKG_BIN" -maxdepth 1 -name "*.dll" -exec cp -v {} "$BIN_DIR/" \;
else
echo "Warning: vcpkg bin dir not found at $VCPKG_BIN"
fi
- name: Prune unused DLLs from build/bin
working-directory: ./citron
run: |
# CopyMinGWDeps.cmake pulls in the full transitive closure of citron.exe
# through /ucrt64/bin, including many DLLs that are never actually loaded.
#
# We compute the true required set using a full recursive objdump closure:
# starting from the citron binaries, we follow every DLL import reference
# and collect any DLL that is present in build/bin. We repeat until no
# new DLLs are added. Anything not reached by this walk is deleted.
BIN_DIR="./build/bin"
echo "=== DLLs before pruning: $(find "$BIN_DIR" -maxdepth 1 -name "*.dll" | wc -l) ==="
# Build a lookup of lowercase dll name -> path for every DLL in bin/
declare -A bin_dlls
while IFS= read -r dll_path; do
dll_name=$(basename "$dll_path" | tr '[:upper:]' '[:lower:]')
bin_dlls["$dll_name"]="$dll_path"
done < <(find "$BIN_DIR" -maxdepth 1 -name "*.dll")
# Closure: set of lowercase dll names known to be required
declare -A closure
# Queue: dll names to process
declare -a queue
# Seed with the citron executables
for exe in "$BIN_DIR"/citron.exe "$BIN_DIR"/citron-cmd.exe "$BIN_DIR"/citron-room.exe; do
[[ -f "$exe" ]] || continue
while IFS= read -r dep; do
dep_lower=$(echo "$dep" | tr '[:upper:]' '[:lower:]')
if [[ -n "${bin_dlls[$dep_lower]+_}" && -z "${closure[$dep_lower]+_}" ]]; then
closure["$dep_lower"]=1
queue+=("$dep_lower")
fi
done < <(objdump -p "$exe" 2>/dev/null | awk '/DLL Name:/{print $3}')
done
# BFS: expand through each newly discovered DLL in bin/
while [[ ${#queue[@]} -gt 0 ]]; do
current="${queue[0]}"
queue=("${queue[@]:1}")
dll_path="${bin_dlls[$current]}"
while IFS= read -r dep; do
dep_lower=$(echo "$dep" | tr '[:upper:]' '[:lower:]')
if [[ -n "${bin_dlls[$dep_lower]+_}" && -z "${closure[$dep_lower]+_}" ]]; then
closure["$dep_lower"]=1
queue+=("$dep_lower")
fi
done < <(objdump -p "$dll_path" 2>/dev/null | awk '/DLL Name:/{print $3}')
done
echo "Closure size: ${#closure[@]} DLLs required"
# Remove everything in bin/ that is not in the closure
removed=0
kept=0
while IFS= read -r dll_path; do
dll_lower=$(basename "$dll_path" | tr '[:upper:]' '[:lower:]')
if [[ -z "${closure[$dll_lower]+_}" ]]; then
echo " Pruning: $(basename "$dll_path")"
rm -f "$dll_path"
removed=$((removed + 1))
else
kept=$((kept + 1))
fi
done < <(find "$BIN_DIR" -maxdepth 1 -name "*.dll")
echo "=== Pruned $removed, kept $kept ==="
echo "=== Final DLL list ==="
find "$BIN_DIR" -maxdepth 1 -name "*.dll" | sort | xargs -I{} basename {}
- name: Package Windows Build
shell: pwsh
run: |