262 lines
12 KiB
Bash
Executable File
262 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
||
set -eo pipefail
|
||
|
||
LOG_FILE="build_win.log"
|
||
> "$LOG_FILE"
|
||
|
||
log() { echo "$1"; echo "$1" >> "$LOG_FILE"; }
|
||
|
||
log "================================"
|
||
log "ChatGUI Build Script (MSYS2 UCRT64)"
|
||
log "================================"
|
||
log ""
|
||
|
||
SRC_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
BUILD_DIR="$SRC_DIR/build"
|
||
RUNTIME_PREFIX="${MSYSTEM_PREFIX:-/ucrt64}"
|
||
JOBS=$(nproc 2>/dev/null || echo 4)
|
||
BUILD_INSTALLER=0
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--installer) BUILD_INSTALLER=1 ;;
|
||
-j)
|
||
[[ $# -gt 1 ]] || { log "ERROR: -j requires a positive job count"; exit 1; }
|
||
shift; JOBS=$1 ;;
|
||
-j*) JOBS=${1#-j} ;;
|
||
*) log "ERROR: unknown argument: $1"; exit 1 ;;
|
||
esac
|
||
shift
|
||
done
|
||
[[ "$JOBS" =~ ^[1-9][0-9]*$ ]] || { log "ERROR: invalid job count: $JOBS"; exit 1; }
|
||
CMAKE_ARGS=()
|
||
if [[ "$BUILD_INSTALLER" = 1 ]]; then
|
||
bash "$SRC_DIR/package_win.sh" --check 2>&1 | tee -a "$LOG_FILE"
|
||
CMAKE_ARGS+=(-DCMAKE_BUILD_TYPE=Release)
|
||
fi
|
||
|
||
log "Source: $SRC_DIR"
|
||
log "Build: $BUILD_DIR"
|
||
log ""
|
||
|
||
if [ -f "$BUILD_DIR/CMakeCache.txt" ]; then
|
||
OLD_GEN=$(grep 'CMAKE_GENERATOR:' "$BUILD_DIR/CMakeCache.txt" 2>/dev/null | cut -d= -f2)
|
||
if [ "$OLD_GEN" != "Ninja" ]; then
|
||
log "Generator changed ($OLD_GEN -> Ninja), cleaning build dir..."
|
||
rm -rf "$BUILD_DIR"
|
||
fi
|
||
fi
|
||
|
||
# Старые DLL приложения в build могут мешать запуску cc1/cc1plus после обновления MSYS2.
|
||
source "$SRC_DIR/compiler_runtime.sh"
|
||
if [ "${MSYSTEM:-}" = UCRT64 ]; then
|
||
chatgui_refresh_compiler_runtime "$BUILD_DIR" "$RUNTIME_PREFIX/bin" 2>&1 | tee -a "$LOG_FILE"
|
||
log "Preparing bundled rlottie..."
|
||
cmake -S "$SRC_DIR/rlottie_src" -B "$SRC_DIR/rlottie_build" -G Ninja \
|
||
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON 2>&1 | tee -a "$LOG_FILE" || {
|
||
log " ERROR: bundled rlottie configure failed"; exit 1;
|
||
}
|
||
cmake --build "$SRC_DIR/rlottie_build" --target rlottie --parallel "$JOBS" 2>&1 | tee -a "$LOG_FILE" || {
|
||
log " ERROR: bundled rlottie build failed"; exit 1;
|
||
}
|
||
log " OK: bundled rlottie ready in $SRC_DIR/rlottie_build"
|
||
fi
|
||
|
||
log "[1/4] Configuring CMake (generator: Ninja)..."
|
||
cmake -S "$SRC_DIR" -B "$BUILD_DIR" -G Ninja "${CMAKE_ARGS[@]}" 2>&1 | tee -a "$LOG_FILE"
|
||
test ${PIPESTATUS[0]} -eq 0 || { log "CMake configure FAILED"; exit 1; }
|
||
|
||
log ""
|
||
log "[2/4] Building (parallel: $JOBS jobs)..."
|
||
cmake --build "$BUILD_DIR" -j"$JOBS" 2>&1 | tee -a "$LOG_FILE"
|
||
test ${PIPESTATUS[0]} -eq 0 || { log "Build FAILED"; exit 1; }
|
||
|
||
log ""
|
||
log "[3/4] Deploying runtime DLLs..."
|
||
RLOTTIE_DLL="$SRC_DIR/rlottie_build/librlottie.dll"
|
||
[ -f "$RLOTTIE_DLL" ] || { log " ERROR: librlottie.dll not found: $RLOTTIE_DLL"; exit 1; }
|
||
cp "$RLOTTIE_DLL" "$BUILD_DIR/librlottie.dll" || {
|
||
log " ERROR: cannot copy $RLOTTIE_DLL -> $BUILD_DIR/librlottie.dll"; exit 1;
|
||
}
|
||
log " OK: librlottie.dll (from $RLOTTIE_DLL; copied to $BUILD_DIR/librlottie.dll)"
|
||
FFMPEG_BIN="$SRC_DIR/ffmpeg/ffmpeg_build/install/bin"
|
||
QT_PACKAGE_FILE="$BUILD_DIR/chatgui_qt_package.txt"
|
||
[ -f "$QT_PACKAGE_FILE" ] || { log " ERROR: Qt package information missing: $QT_PACKAGE_FILE"; exit 1; }
|
||
QT_PACKAGE=$(tr -d '\r\n' < "$QT_PACKAGE_FILE")
|
||
case "$QT_PACKAGE" in
|
||
Qt5|Qt6) QT_MAJOR="${QT_PACKAGE#Qt}" ;;
|
||
*) log " ERROR: invalid configured Qt package: $QT_PACKAGE"; exit 1 ;;
|
||
esac
|
||
log " Runtime package: $QT_PACKAGE"
|
||
MAIN_DLLS=(
|
||
"Qt${QT_MAJOR}Core.dll" "Qt${QT_MAJOR}Gui.dll" "Qt${QT_MAJOR}Network.dll" "Qt${QT_MAJOR}Widgets.dll"
|
||
"platforms/qwindows.dll" "imageformats/qjpeg.dll" "imageformats/qwebp.dll"
|
||
"libstdc++-6.dll" "libgcc_s_*.dll" "libwinpthread-1.dll" "librlottie.dll" "zlib1.dll" "libcrypto-*.dll"
|
||
)
|
||
if [ -d "$FFMPEG_BIN" ]; then
|
||
MAIN_DLLS+=("avcodec-*.dll" "avformat-*.dll" "avutil-*.dll" "swscale-*.dll" "swresample-*.dll")
|
||
fi
|
||
DLLS_PRESENT=1
|
||
for d in "${MAIN_DLLS[@]}"; do
|
||
if ! compgen -G "$BUILD_DIR/$d" >/dev/null; then
|
||
log " Main DLL missing: $d; deploying runtime DLLs"
|
||
DLLS_PRESENT=0
|
||
break
|
||
fi
|
||
done
|
||
[ "$DLLS_PRESENT" -ne 1 ] || log " Main runtime DLLs present; verifying their dependencies"
|
||
# Проверяем зависимости на каждом запуске: основные DLL не гарантируют полный комплект.
|
||
{
|
||
WINDEPLOYQT=""
|
||
for candidate in "$RUNTIME_PREFIX/bin/windeployqt${QT_MAJOR}.exe" "$RUNTIME_PREFIX/bin/windeployqt-qt${QT_MAJOR}.exe" "$RUNTIME_PREFIX/bin/windeployqt.exe"; do
|
||
[ -x "$candidate" ] || continue
|
||
tool_version=$("$candidate" --version 2>&1) || { log " WARN: cannot query $candidate: $tool_version"; continue; }
|
||
if [[ "$tool_version" =~ (^|[[:space:]])${QT_MAJOR}\.[0-9]+ ]]; then WINDEPLOYQT="$candidate"; break; fi
|
||
log " WARN: ignoring $candidate ($tool_version); build uses $QT_PACKAGE"
|
||
done
|
||
if [ -x "$WINDEPLOYQT" ]; then
|
||
log " Running $WINDEPLOYQT..."
|
||
if "$WINDEPLOYQT" --no-translations --no-compiler-runtime "$BUILD_DIR/vibechat.exe" 2>&1 | tee -a "$LOG_FILE"; then
|
||
log " OK: windeployqt completed"
|
||
else
|
||
RC=${PIPESTATUS[0]}
|
||
log " WARN: windeployqt failed (exit $RC); verifying direct DLL deployment"
|
||
fi
|
||
else
|
||
log " WARN: matching $QT_PACKAGE windeployqt not found; copying dependencies directly"
|
||
fi
|
||
|
||
# Основные Qt DLL и динамические плагины всегда берём из выбранного комплекта.
|
||
for component in Core Gui Network Widgets; do
|
||
qt_dll="Qt${QT_MAJOR}${component}.dll"
|
||
cp "$RUNTIME_PREFIX/bin/$qt_dll" "$BUILD_DIR/" || { log " ERROR: cannot deploy $qt_dll from $RUNTIME_PREFIX/bin"; exit 1; }
|
||
done
|
||
for plugin in platforms/qwindows.dll imageformats/qjpeg.dll imageformats/qwebp.dll; do
|
||
plugin_source=""
|
||
for dir in "$RUNTIME_PREFIX/share/qt${QT_MAJOR}/plugins" "$RUNTIME_PREFIX/lib/qt${QT_MAJOR}/plugins"; do
|
||
[ -f "$dir/$plugin" ] && { plugin_source="$dir/$plugin"; break; }
|
||
done
|
||
[ -n "$plugin_source" ] || { log " ERROR: $QT_PACKAGE plugin missing: $plugin"; exit 1; }
|
||
mkdir -p "$BUILD_DIR/${plugin%/*}"
|
||
cp "$plugin_source" "$BUILD_DIR/$plugin" || { log " ERROR: cannot deploy $plugin_source"; exit 1; }
|
||
log " OK: $plugin (from $plugin_source)"
|
||
done
|
||
|
||
# Резолвим НЕ-Qt зависимости (FFmpeg, mingw-runtime): windeployqt копирует только Qt.
|
||
# FFmpeg ищем ПЕРВЫМ в $FFMPEG_BIN: exe линкован именно под кастомный билд,
|
||
# а pacman-ffmpeg той же мажорной версии в /ucrt64/bin дал бы неверный набор кодеков.
|
||
OBJDUMP="objdump"; [ -n "${MSYSTEM_PREFIX:-}" ] && OBJDUMP="$MSYSTEM_PREFIX/bin/objdump"
|
||
command -v "$OBJDUMP" >/dev/null 2>&1 || { log " ERROR: $OBJDUMP not found"; exit 1; }
|
||
shopt -s nocasematch nullglob
|
||
# Обновляем весь комплект FFmpeg, включая DLL, не импортируемые напрямую приложением.
|
||
for src in "$FFMPEG_BIN/"*.dll; do
|
||
# Общий runtime берём из установленного UCRT64, а кодеки — из собственной сборки FFmpeg.
|
||
if chatgui_is_compiler_runtime_dll "${src##*/}"; then
|
||
src="$RUNTIME_PREFIX/bin/${src##*/}"
|
||
fi
|
||
cp "$src" "$BUILD_DIR/" || { log " ERROR: failed to copy $src"; exit 1; }
|
||
log " OK: $(basename "$src") (from $src)"
|
||
done
|
||
log " Resolving non-Qt dependencies recursively..."
|
||
log " Windows system DLLs are provided by the OS and excluded from deployment"
|
||
# Проверяем также уже скопированные DLL: наличие файла не гарантирует наличие его зависимостей.
|
||
todo=("$BUILD_DIR/vibechat.exe" "$BUILD_DIR/"*.dll "$BUILD_DIR/platforms/"*.dll "$BUILD_DIR/imageformats/"*.dll)
|
||
declare -A scanned=()
|
||
MISSING=0
|
||
# Большой вывод objdump передаём через файл, чтобы не зависеть от here-string и pipe в MSYS2.
|
||
IMPORT_DUMP=$(mktemp) || {
|
||
log " ERROR: cannot create temporary file for DLL inspection"
|
||
exit 1
|
||
}
|
||
trap 'rm -f -- "$IMPORT_DUMP" || log " WARN: cannot remove temporary import dump: $IMPORT_DUMP"' EXIT
|
||
for ((i = 0; i < ${#todo[@]}; i++)); do
|
||
src="${todo[i]}"
|
||
key="${src,,}"
|
||
[ -n "${scanned[$key]:-}" ] && continue
|
||
scanned[$key]=1
|
||
CHECK_STARTED=$SECONDS
|
||
"$OBJDUMP" -p "$src" > "$IMPORT_DUMP" 2>&1 || {
|
||
RC=$?
|
||
log " ERROR: cannot inspect $src (exit=$RC, elapsed=$((SECONDS - CHECK_STARTED))s): $(cat "$IMPORT_DUMP")"
|
||
MISSING=$((MISSING + 1))
|
||
continue
|
||
}
|
||
PARSE_STARTED=$SECONDS
|
||
import_list=$(awk '/DLL Name:/ {print $3}' "$IMPORT_DUMP" 2>&1) || {
|
||
RC=$?
|
||
log " ERROR: cannot parse imports for $src (exit=$RC, elapsed=$((SECONDS - PARSE_STARTED))s): $import_list"
|
||
MISSING=$((MISSING + 1))
|
||
continue
|
||
}
|
||
while read -r dep; do
|
||
[ -z "$dep" ] && continue
|
||
case "$dep" in
|
||
kernel32.dll|msvcrt.dll|msvcp140.dll|vcruntime140.dll|ucrtbase.dll|\
|
||
user32.dll|gdi32.dll|gdi32full.dll|shell32.dll|shlwapi.dll|shcore.dll|advapi32.dll|\
|
||
ole32.dll|oleaut32.dll|comdlg32.dll|comctl32.dll|ws2_32.dll|wsock32.dll|\
|
||
iphlpapi.dll|netapi32.dll|dnsapi.dll|winhttp.dll|winmm.dll|imm32.dll|version.dll|\
|
||
setupapi.dll|crypt32.dll|authz.dll|bcrypt.dll|bcryptprimitives.dll|ncrypt.dll|dbghelp.dll|imagehlp.dll|kernelbase.dll|\
|
||
rpcrt4.dll|psapi.dll|powrprof.dll|cfgmgr32.dll|userenv.dll|secur32.dll|\
|
||
uxtheme.dll|dwmapi.dll|d3d9.dll|d3d11.dll|d3d12.dll|dxgi.dll|opengl32.dll|glu32.dll|\
|
||
wtsapi32.dll|mpr.dll|ntdll.dll|avicap32.dll|msimg32.dll|wldap32.dll|gdiplus.dll|usp10.dll|\
|
||
dwrite.dll|winspool.drv|api-ms-win-*)
|
||
continue ;;
|
||
esac
|
||
if [ ! -f "$BUILD_DIR/$dep" ]; then
|
||
dep_src=""
|
||
dep_dirs=("$FFMPEG_BIN" "$RUNTIME_PREFIX/bin" "$SRC_DIR/rlottie_build")
|
||
if chatgui_is_compiler_runtime_dll "$dep"; then
|
||
dep_dirs=("$RUNTIME_PREFIX/bin")
|
||
fi
|
||
for dir in "${dep_dirs[@]}"; do
|
||
if [ -f "$dir/$dep" ]; then
|
||
dep_src="$dir/$dep"
|
||
break
|
||
fi
|
||
done
|
||
if [ -z "$dep_src" ]; then
|
||
log " ERROR: $src requires $dep; not found in FFmpeg, UCRT64 or rlottie directories"
|
||
MISSING=$((MISSING + 1))
|
||
continue
|
||
fi
|
||
cp "$dep_src" "$BUILD_DIR/" || { log " ERROR: failed to copy $dep_src"; exit 1; }
|
||
log " OK: $dep (from $dep_src; required by $src)"
|
||
fi
|
||
todo+=("$BUILD_DIR/$dep")
|
||
done <<< "$import_list"
|
||
done
|
||
shopt -u nocasematch nullglob
|
||
[ "$MISSING" -eq 0 ] || { log " ERROR: runtime DLL deployment incomplete"; exit 1; }
|
||
# Проверяем тот же основной комплект после копирования.
|
||
log " Verifying main runtime DLLs..."
|
||
MISSING=0
|
||
for d in "${MAIN_DLLS[@]}"; do
|
||
if ! compgen -G "$BUILD_DIR/$d" >/dev/null; then
|
||
log " ERROR: $d MISSING in build dir"; MISSING=1
|
||
fi
|
||
done
|
||
if [ "$MISSING" -eq 1 ]; then
|
||
log " ERROR: required runtime DLLs missing"; exit 1
|
||
fi
|
||
}
|
||
cp "$SRC_DIR/setup_firewall.bat" "$BUILD_DIR/" 2>/dev/null && log " OK: setup_firewall.bat" || log " WARN: setup_firewall.bat not found"
|
||
|
||
log ""
|
||
log "[4/4] Verifying..."
|
||
EXE="$BUILD_DIR/vibechat.exe"
|
||
if [ -f "$EXE" ]; then
|
||
SIZE=$(du -h "$EXE" | cut -f1)
|
||
log " OK: vibechat.exe ($SIZE)"
|
||
if [[ "$BUILD_INSTALLER" = 1 ]]; then
|
||
log "Creating Windows installer..."
|
||
bash "$SRC_DIR/package_win.sh" 2>&1 | tee -a "$LOG_FILE"
|
||
fi
|
||
log ""
|
||
log "================================"
|
||
log "Build SUCCESS"
|
||
log "================================"
|
||
log "Executable: $EXE"
|
||
else
|
||
log " FAIL: vibechat.exe not found"
|
||
exit 1
|
||
fi
|