Reuse PE cache during packaging and test installer completeness

This commit is contained in:
evgeny committed 2026-10-30 11:00:08 +01:00
1 parent 0bee366d6b
commit 359c6bc843
3 files changed
+61 -16

No files matched your search

+18 -6
View File
@@ -2,6 +2,8 @@
# Чистый комплект из Windows-сборки и Inno Setup; ключи/БД/логи из build не копируются.
set -euo pipefail
log() { echo "$1"; }
SRC_DIR=$(cd "$(dirname "$0")" && pwd)
BUILD_DIR="$SRC_DIR/build"
RUNTIME_PREFIX="${MSYSTEM_PREFIX:-/ucrt64}"
@@ -70,6 +72,10 @@ cp "$SRC_DIR/../../lib/wintun-LICENSE.txt" "$SRC_DIR/../../lib/wintun-api-LICENS
# Проверяем архитектуру/полноту уже собранного комплекта, включая динамические Qt-плагины.
SYSTEM_DIR=$(cygpath -u "${WINDIR:-C:\\Windows}/System32")
source "$SRC_DIR/compiler_runtime.sh"
source "$SRC_DIR/runtime_cache.sh"
runtime_cache_init
IMPORT_DUMP="$STAGE/imports.txt"
VERIFY_STARTED=$SECONDS
declare -A LICENSE_PACKAGES=()
declare -A INCLUDED=()
FILES=(vibechat.exe wintun.dll platforms/qwindows.dll imageformats/qjpeg.dll imageformats/qwebp.dll)
@@ -84,14 +90,19 @@ for ((i = 0; i < ${#FILES[@]}; i++)); do
echo "ERROR: mixed Qt runtime: $relative does not belong to $QT_PACKAGE" >&2
exit 1
fi
# Ключ кеша относится к build, а не к новой временной копии установщика.
runtime_inspect "$BUILD_DIR/$relative"
[[ "$binary_format" = pei-x86-64 ]] || { echo "ERROR: $name is not a Windows x64 binary: $binary_format" >&2; exit 1; }
file="$PACKAGE_DIR/$relative"
mkdir -p "${file%/*}"
cp "$BUILD_DIR/$relative" "$file"
name=${file##*/}
objdump -f "$file" > "$STAGE/headers.txt"
binary_format=$(awk '/file format/ {print $NF}' "$STAGE/headers.txt")
[[ "$binary_format" = pei-x86-64 ]] || { echo "ERROR: $name is not a Windows x64 binary: $binary_format" >&2; exit 1; }
objdump -p "$file" > "$STAGE/imports.txt"
# Успешный cp переносит проверенные байты; источник не должен меняться во время анализа/копирования.
copied_stamp=$(stat -Lc '%n|%s|%y|%z' -- "$BUILD_DIR/$relative") || {
echo "ERROR: cannot stat installer source: $relative" >&2; exit 1;
}
[[ "$copied_stamp" = "$binary_stamp" ]] || {
echo "ERROR: installer source changed during packaging: $relative; rebuild and retry" >&2; exit 1;
}
while read -r dep; do
[[ -n "$dep" ]] || continue
if [[ -f "$BUILD_DIR/$dep" ]]; then FILES+=("$dep"); continue; fi
@@ -99,7 +110,7 @@ for ((i = 0; i < ${#FILES[@]}; i++)); do
if [[ -f "$SYSTEM_DIR/$dep" && "$dep" != msvcp*.dll && "$dep" != vcruntime*.dll ]]; then continue; fi
echo "ERROR: incomplete installer runtime: $name requires $dep" >&2
exit 1
done < <(awk '/DLL Name:/ {print $3}' "$STAGE/imports.txt")
done <<< "$import_list"
echo "INFO: verified installer runtime: ${file#"$PACKAGE_DIR/"}"
# Лицензии внешних библиотек берём из пакетов той же UCRT64-сборки.
@@ -119,6 +130,7 @@ for ((i = 0; i < ${#FILES[@]}; i++)); do
[[ -n "$pkg" ]] || { echo "ERROR: no package owns runtime file: $runtime_file" >&2; exit 1; }
LICENSE_PACKAGES["$pkg"]=1
done
echo "INFO: installer PE verification: scanned=$RUNTIME_SCANNED cached=$RUNTIME_HITS elapsed=$((SECONDS - VERIFY_STARTED))s"
for pkg in "${!LICENSE_PACKAGES[@]}"; do
pacman -Qql "$pkg" > "$STAGE/package-files.txt"
while IFS= read -r file; do
+2 -1
View File
@@ -27,6 +27,7 @@ cat > "$fixture_root/commands/objdump" <<'SH'
#!/bin/bash
printf '%s\n' "$2" >> "$DEPLOY_OBJDUMP_LOG"
if [[ "$(< "$2")" = *BROKEN* ]]; then echo 'invalid PE fixture' >&2; exit 7; fi
printf '%s: file format pei-x86-64\n' "$2"
cat "$2"
SH
chmod +x "$fixture_root/commands/"*
@@ -35,7 +36,7 @@ prepare_case() {
case_dir="$fixture_root/project/Qt$1 package"; prefix="$case_dir/ucrt64"
mkdir -p "$case_dir/rlottie_build" "$prefix/bin" "$prefix/share/qt$1/plugins/platforms" "$prefix/share/qt$1/plugins/imageformats"
cp "$fixture_root/commands/objdump" "$prefix/bin/objdump"
cp "$source_dir/build.sh" "$source_dir/compiler_runtime.sh" "$source_dir/runtime_cache.sh" "$case_dir/"
cp "$source_dir/build.sh" "$source_dir/package_win.sh" "$source_dir/compiler_runtime.sh" "$source_dir/runtime_cache.sh" "$case_dir/"
printf 'DLL Name: KERNEL32.dll\n' > "$case_dir/rlottie_build/librlottie.dll"
printf 'fixture\n' > "$case_dir/setup_firewall.bat"
printf 'DLL Name: AUTHZ.dll\n' > "$prefix/bin/Qt$1Core.dll"
+41 -9
View File
@@ -14,7 +14,9 @@ gui = case / 'tools/chatgui'
build = gui / 'build'
commands = root / 'commands'
prefix = root / 'ucrt64'
environment = {**os.environ, 'MSYSTEM': 'UCRT64', 'MSYSTEM_PREFIX': str(prefix), 'WINDIR': str(root / 'Windows')}
scan_log = root / 'objdump.log'
environment = {**os.environ, 'MSYSTEM': 'UCRT64', 'MSYSTEM_PREFIX': str(prefix), 'WINDIR': str(root / 'Windows'),
'OBJDUMP_CALLS': str(scan_log)}
def write(path, text, executable=False):
@@ -26,6 +28,7 @@ def write(path, text, executable=False):
def run(*arguments, ok=True, extra=None):
scan_log.write_text('')
result = subprocess.run(['bash', str(gui / 'package_win.sh'), *arguments],
env={**environment, **(extra or {})}, text=True, capture_output=True)
with (root / 'checks.log').open('a') as log:
@@ -46,7 +49,7 @@ def package():
try:
for name in ('package_win.sh', 'compiler_runtime.sh', 'build.sh', 'installer/vibechat.iss'):
for name in ('package_win.sh', 'compiler_runtime.sh', 'runtime_cache.sh', 'build.sh', 'installer/vibechat.iss'):
target = gui / name
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source / name, target)
@@ -62,12 +65,13 @@ try:
write(commands / 'cygpath', '#!/bin/bash\nprintf "%s\\n" "$2"\n', executable=True)
write(commands / 'objdump', '''#!/bin/bash
set -eu
if [[ "$1" = -f ]]; then
if [[ $(head -1 "$2") = wrong-architecture ]]; then echo 'file format pei-i386'; else echo 'file format pei-x86-64'; fi
else
cat "$2"
fi
[[ "$1" = -p ]] || { echo 'unexpected extra PE inspection' >&2; exit 1; }
printf '%s\\n' "$2" >> "$OBJDUMP_CALLS"
if [[ $(head -1 "$2") = wrong-architecture ]]; then echo 'file format pei-i386'; else echo 'file format pei-x86-64'; fi
cat "$2"
''', executable=True)
(prefix / 'bin').mkdir(parents=True)
shutil.copy(commands / 'objdump', prefix / 'bin/objdump')
write(commands / 'pacman', '''#!/bin/bash
set -eu
if [[ "$1" = -Qqo ]]; then echo fixture-runtime; else echo "$MSYSTEM_PREFIX/share/licenses/fixture/COPYING"; fi
@@ -119,11 +123,30 @@ files = sorted(str(p.relative_to(package)) for p in package.rglob('*') if p.is_f
'licenses/wintun/wintun-LICENSE.txt', 'licenses/wintun/wintun-api-LICENSE.txt',
'licenses/rlottie/COPYING', 'licenses/rlottie/licenses/COPYING.MIT', 'licenses/runtime/fixture/COPYING'}
assert set(result['files']) == expected, result['files']
assert len(scan_log.read_text().splitlines()) == len(libraries) + 1, 'Expected one PE inspection per packaged binary'
print('PASS: complete dependency graph, Qt plugin helpers and license notices; private files and stale DLLs excluded')
result2 = package()
assert result2['files'] == result['files']
print('PASS: repeated packaging and paths containing spaces')
assert not scan_log.read_text(), 'Repeated packaging inspected unchanged PE files'
print('PASS: repeated packaging reuses architecture and imports with no objdump calls; paths containing spaces')
write(build / 'vibechat.exe', (build / 'vibechat.exe').read_text() + '\nrelinked\n')
assert package()['files'] == result['files']
assert scan_log.read_text().splitlines() == [str(build / 'vibechat.exe')]
print('PASS: standalone packaging inspects only a changed EXE')
# Проверенные импорты относятся к конкретным байтам: изменение источника при копировании — ошибка.
write(commands / 'cp', '''#!/bin/bash
set -eu
/bin/cp "$@"
if [[ ${MUTATE_COPY_SOURCE:-} = "$1" ]]; then printf '\\nchanged during copy\\n' >> "$1"; fi
''', executable=True)
source_before = (build / 'helper.dll').read_text()
failed = run(ok=False, extra={'MUTATE_COPY_SOURCE': str(build / 'helper.dll')})
assert 'installer source changed during packaging: helper.dll' in failed.stderr
write(build / 'helper.dll', source_before)
print('PASS: source mutation during staging rejects the installer')
driver = build / 'wintun.dll'
saved_driver = driver.read_text()
@@ -133,6 +156,9 @@ files = sorted(str(p.relative_to(package)) for p in package.rglob('*') if p.is_f
write(driver, 'wrong-architecture\n')
failed = run(ok=False)
assert 'wintun.dll is not a Windows x64 binary' in failed.stderr
failed = run(ok=False)
assert 'wintun.dll is not a Windows x64 binary' in failed.stderr
assert not scan_log.read_text(), 'Cached architecture rejection required another inspection'
write(driver, saved_driver)
print('PASS: dynamically loaded Wintun and its licenses are included; missing/wrong architecture DLL is rejected')
@@ -158,8 +184,9 @@ files = sorted(str(p.relative_to(package)) for p in package.rglob('*') if p.is_f
missing.unlink()
failed = run(ok=False)
assert 'qjpeg.dll requires jpeg-helper.dll' in failed.stderr
assert str(build / 'imageformats/qjpeg.dll') not in scan_log.read_text().splitlines()
write(missing, saved)
print('PASS: missing transitive plugin dependency rejects incomplete installer')
print('PASS: cached plugin imports still reject a missing transitive dependency')
write(build / 'helper.dll', 'wrong-architecture\n')
failed = run(ok=False)
@@ -232,13 +259,18 @@ if [[ ${FFMPEG_FAIL:-0} = 1 ]]; then exit 1; fi
''')
environment['FFMPEG_CALLS'] = str(ffmpeg_calls)
(build / 'wintun.dll').unlink()
scan_log.write_text('')
built = subprocess.run(['bash', str(gui / 'build.sh'), '--installer', '-j2'], cwd=gui,
env={**environment, 'CMAKE_CALLS': str(calls)}, text=True, capture_output=True)
assert built.returncode == 0, built.stdout + built.stderr
assert '-DCMAKE_BUILD_TYPE=Release' in calls.read_text()
assert 'installer created:' in built.stdout and 'Build SUCCESS' in built.stdout
assert 'installer PE verification: scanned=0 cached=' in built.stdout, built.stdout
scans = scan_log.read_text().splitlines()
assert len(scans) == len(set(scans)), 'Packaging repeated a PE inspection performed by deployment'
assert (build / 'wintun.dll').read_bytes() == (case / 'lib/wintun.dll').read_bytes()
assert ffmpeg_calls.read_text().splitlines() == ['profile update']
print('PASS: build --installer shares cached PE format/imports without rescanning DLLs in staging')
cmake_before_failure = calls.read_text()
codec_failed = subprocess.run(['bash', str(gui / 'build.sh'), '--installer', '-j2'], cwd=gui,
env={**environment, 'CMAKE_CALLS': str(calls), 'FFMPEG_FAIL': '1'}, text=True, capture_output=True)