Batch Windows installer staging and license queries
This commit is contained in:
1 parent
f87f70489a
commit
050b2f6d05
2 files changed
+79
-32
No files matched your search
@@ -78,6 +78,7 @@ IMPORT_DUMP="$STAGE/imports.txt"
|
||||
VERIFY_STARTED=$SECONDS
|
||||
declare -A LICENSE_PACKAGES=()
|
||||
declare -A INCLUDED=()
|
||||
PACKAGE_FILES=() PACKAGE_SOURCES=() PACKAGE_STAMPS=() LICENSE_SOURCES=()
|
||||
FILES=(vibechat.exe wintun.dll platforms/qwindows.dll imageformats/qjpeg.dll imageformats/qwebp.dll)
|
||||
FILES+=("${CAPTURE_RUNTIME[@]}")
|
||||
HAS_FFMPEG=0
|
||||
@@ -93,16 +94,9 @@ for ((i = 0; i < ${#FILES[@]}; i++)); do
|
||||
# Ключ кеша относится к 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"
|
||||
# Успешный 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;
|
||||
}
|
||||
PACKAGE_FILES+=("$relative")
|
||||
PACKAGE_SOURCES+=("$BUILD_DIR/$relative")
|
||||
PACKAGE_STAMPS+=("$binary_stamp")
|
||||
while read -r dep; do
|
||||
[[ -n "$dep" ]] || continue
|
||||
if [[ -f "$BUILD_DIR/$dep" ]]; then FILES+=("$dep"); continue; fi
|
||||
@@ -111,8 +105,6 @@ for ((i = 0; i < ${#FILES[@]}; i++)); do
|
||||
echo "ERROR: incomplete installer runtime: $name requires $dep" >&2
|
||||
exit 1
|
||||
done <<< "$import_list"
|
||||
echo "INFO: verified installer runtime: ${file#"$PACKAGE_DIR/"}"
|
||||
|
||||
# Лицензии внешних библиотек берём из пакетов той же UCRT64-сборки.
|
||||
[[ "$name" != vibechat.exe && "$name" != librlottie.dll && "$name" != wintun.dll ]] || continue
|
||||
if [[ -f "$SRC_DIR/ffmpeg/ffmpeg_build/install/bin/$name" ]] && ! chatgui_is_compiler_runtime_dll "$name"; then
|
||||
@@ -120,27 +112,47 @@ for ((i = 0; i < ${#FILES[@]}; i++)); do
|
||||
continue
|
||||
fi
|
||||
runtime_file="$RUNTIME_PREFIX/bin/$name"
|
||||
if [[ "$file" = "$PACKAGE_DIR/platforms/"* || "$file" = "$PACKAGE_DIR/imageformats/"* || "$file" = "$PACKAGE_DIR/multimedia/"* ]]; then
|
||||
relative=${file#"$PACKAGE_DIR/"}
|
||||
if [[ "$relative" = platforms/* || "$relative" = imageformats/* || "$relative" = multimedia/* ]]; then
|
||||
runtime_file="$RUNTIME_PREFIX/share/qt${QT_PACKAGE#Qt}/plugins/$relative"
|
||||
[[ -f "$runtime_file" ]] || runtime_file="$RUNTIME_PREFIX/lib/qt${QT_PACKAGE#Qt}/plugins/$relative"
|
||||
fi
|
||||
[[ -f "$runtime_file" ]] || { echo "ERROR: cannot identify runtime source for $name" >&2; exit 1; }
|
||||
pkg=$(pacman -Qqo "$runtime_file")
|
||||
[[ -n "$pkg" ]] || { echo "ERROR: no package owns runtime file: $runtime_file" >&2; exit 1; }
|
||||
LICENSE_PACKAGES["$pkg"]=1
|
||||
LICENSE_SOURCES+=("$runtime_file")
|
||||
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"
|
||||
# Один cp вместо отдельных mkdir/cp на каждую DLL; --parents сохраняет каталоги Qt-плагинов.
|
||||
(cd "$BUILD_DIR" && cp --parents -- "${PACKAGE_FILES[@]}" "$PACKAGE_DIR/")
|
||||
# Проверяем одним stat, что все источники остались теми же после анализа и копирования.
|
||||
copied_stamps=$(stat -Lc '%n|%s|%y|%z' -- "${PACKAGE_SOURCES[@]}") || {
|
||||
echo "ERROR: cannot stat installer sources after copying" >&2; exit 1;
|
||||
}
|
||||
mapfile -t COPIED_STAMPS <<< "$copied_stamps"
|
||||
for ((i = 0; i < ${#PACKAGE_FILES[@]}; i++)); do
|
||||
[[ "${COPIED_STAMPS[i]:-}" = "${PACKAGE_STAMPS[i]}" ]] || {
|
||||
echo "ERROR: installer source changed during packaging: ${PACKAGE_FILES[i]}; rebuild and retry" >&2; exit 1;
|
||||
}
|
||||
done
|
||||
echo "INFO: installer runtime staged: files=${#PACKAGE_FILES[@]} scanned=$RUNTIME_SCANNED cached=$RUNTIME_HITS elapsed=$((SECONDS - VERIFY_STARTED))s"
|
||||
|
||||
# На MSYS2 запуск процессов дорог: все владельцы файлов и списки лицензий запрашиваются пакетно.
|
||||
if (( ${#LICENSE_SOURCES[@]} )); then
|
||||
pacman -Qqo -- "${LICENSE_SOURCES[@]}" > "$STAGE/license-packages.txt" || {
|
||||
echo "ERROR: cannot identify packages owning installer runtime files" >&2; exit 1;
|
||||
}
|
||||
while IFS= read -r pkg; do
|
||||
[[ -z "$pkg" ]] || LICENSE_PACKAGES["$pkg"]=1
|
||||
done < "$STAGE/license-packages.txt"
|
||||
(( ${#LICENSE_PACKAGES[@]} )) || { echo "ERROR: no package owns installer runtime files" >&2; exit 1; }
|
||||
pacman -Qql -- "${!LICENSE_PACKAGES[@]}" > "$STAGE/package-files.txt" || {
|
||||
echo "ERROR: cannot list installer runtime license files" >&2; exit 1;
|
||||
}
|
||||
while IFS= read -r file; do
|
||||
[[ "$file" = */share/licenses/* && -f "$file" ]] || continue
|
||||
relative=${file#*/share/licenses/}
|
||||
mkdir -p "$PACKAGE_DIR/licenses/runtime/${relative%/*}"
|
||||
cp "$file" "$PACKAGE_DIR/licenses/runtime/$relative"
|
||||
done < "$STAGE/package-files.txt"
|
||||
echo "INFO: included runtime license notices: $pkg"
|
||||
done
|
||||
echo "INFO: included runtime license notices: packages=${#LICENSE_PACKAGES[@]}"
|
||||
fi
|
||||
if [[ "$HAS_FFMPEG" = 1 ]]; then
|
||||
FFMPEG_SOURCE="$SRC_DIR/ffmpeg/ffmpeg_build/src"
|
||||
[[ -f "$FFMPEG_SOURCE/ffmpeg/COPYING.GPLv3" ]] || { echo "ERROR: FFmpeg source license notices missing: $FFMPEG_SOURCE" >&2; exit 1; }
|
||||
|
||||
@@ -15,8 +15,10 @@ build = gui / 'build'
|
||||
commands = root / 'commands'
|
||||
prefix = root / 'ucrt64'
|
||||
scan_log = root / 'objdump.log'
|
||||
copy_log = root / 'runtime-copy.log'
|
||||
pacman_log = root / 'pacman.log'
|
||||
environment = {**os.environ, 'MSYSTEM': 'UCRT64', 'MSYSTEM_PREFIX': str(prefix), 'WINDIR': str(root / 'Windows'),
|
||||
'OBJDUMP_CALLS': str(scan_log)}
|
||||
'OBJDUMP_CALLS': str(scan_log), 'RUNTIME_COPY_CALLS': str(copy_log), 'PACMAN_CALLS': str(pacman_log)}
|
||||
|
||||
|
||||
def write(path, text, executable=False):
|
||||
@@ -29,6 +31,8 @@ def write(path, text, executable=False):
|
||||
|
||||
def run(*arguments, ok=True, extra=None):
|
||||
scan_log.write_text('')
|
||||
copy_log.write_text('')
|
||||
pacman_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:
|
||||
@@ -43,7 +47,11 @@ def run(*arguments, ok=True, extra=None):
|
||||
|
||||
|
||||
def package():
|
||||
run()
|
||||
result = run()
|
||||
assert 'verified installer runtime:' not in result.stdout, 'Per-DLL verification spam returned'
|
||||
assert result.stdout.count('INFO: installer runtime staged:') == 1
|
||||
assert copy_log.read_text().splitlines() == ['runtime batch'], 'Expected one runtime copy process'
|
||||
assert pacman_log.read_text().splitlines() == ['-Qqo', '-Qql'], 'Expected two batched package queries'
|
||||
artifact = build / 'installer/VibeChat-1.2.3-win64-setup.exe'
|
||||
return json.loads(artifact.read_text())
|
||||
|
||||
@@ -61,6 +69,7 @@ try:
|
||||
write(gui / 'rlottie_src/COPYING', 'rlottie notice\n')
|
||||
write(gui / 'rlottie_src/licenses/COPYING.MIT', 'MIT notice\n')
|
||||
write(prefix / 'share/licenses/fixture/COPYING', 'runtime notice\n')
|
||||
write(prefix / 'share/licenses/helper/COPYING', 'helper notice\n')
|
||||
write(root / 'Windows/System32/KERNEL32.dll', 'system file\n')
|
||||
write(commands / 'cygpath', '#!/bin/bash\nprintf "%s\\n" "$2"\n', executable=True)
|
||||
write(commands / 'objdump', '''#!/bin/bash
|
||||
@@ -74,7 +83,30 @@ cat "$2"
|
||||
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
|
||||
operation=$1; shift
|
||||
[[ "$1" = -- ]]; shift
|
||||
printf '%s\\n' "$operation" >> "$PACMAN_CALLS"
|
||||
[[ ${PACMAN_FAIL:-} != "$operation" ]] || exit 1
|
||||
for target in "$@"; do
|
||||
if [[ "$operation" = -Qqo ]]; then
|
||||
[[ -f "$target" ]]
|
||||
if [[ "$target" = */helper.dll ]]; then echo fixture-helper; else echo fixture-runtime; fi
|
||||
else
|
||||
case "$target" in
|
||||
fixture-runtime) echo "$MSYSTEM_PREFIX/share/licenses/fixture/COPYING" ;;
|
||||
fixture-helper) echo "$MSYSTEM_PREFIX/share/licenses/helper/COPYING" ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
''', executable=True)
|
||||
write(commands / 'cp', '''#!/bin/bash
|
||||
set -eu
|
||||
/bin/cp "$@"
|
||||
if [[ "$1" = --parents ]]; then
|
||||
printf 'runtime batch\\n' >> "$RUNTIME_COPY_CALLS"
|
||||
if [[ -n ${MUTATE_COPY_SOURCE:-} ]]; then printf '\\nchanged during copy\\n' >> "$MUTATE_COPY_SOURCE"; fi
|
||||
fi
|
||||
''', executable=True)
|
||||
compiler = write(commands / 'Inno Setup/ISCC.exe', '''#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
@@ -121,7 +153,8 @@ files = sorted(str(p.relative_to(package)) for p in package.rglob('*') if p.is_f
|
||||
result = package()
|
||||
expected = {'vibechat.exe', *libraries, 'licenses/LICENSE', 'licenses/LICENSING', 'licenses/THIRD_PARTY_NOTICES',
|
||||
'licenses/wintun/wintun-LICENSE.txt', 'licenses/wintun/wintun-api-LICENSE.txt',
|
||||
'licenses/rlottie/COPYING', 'licenses/rlottie/licenses/COPYING.MIT', 'licenses/runtime/fixture/COPYING'}
|
||||
'licenses/rlottie/COPYING', 'licenses/rlottie/licenses/COPYING.MIT',
|
||||
'licenses/runtime/fixture/COPYING', 'licenses/runtime/helper/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')
|
||||
@@ -130,6 +163,12 @@ files = sorted(str(p.relative_to(package)) for p in package.rglob('*') if p.is_f
|
||||
assert result2['files'] == result['files']
|
||||
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')
|
||||
print('PASS: runtime copied in one batch, license owners/listing queried in two processes, one runtime summary')
|
||||
|
||||
for operation in ('-Qqo', '-Qql'):
|
||||
failed = run(ok=False, extra={'PACMAN_FAIL': operation})
|
||||
assert 'ERROR: cannot ' in failed.stderr
|
||||
print('PASS: failed batched package queries reject incomplete license collection')
|
||||
|
||||
write(build / 'vibechat.exe', (build / 'vibechat.exe').read_text() + '\nrelinked\n')
|
||||
assert package()['files'] == result['files']
|
||||
@@ -137,11 +176,6 @@ files = sorted(str(p.relative_to(package)) for p in package.rglob('*') if p.is_f
|
||||
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
|
||||
@@ -265,7 +299,8 @@ if [[ ${FFMPEG_FAIL:-0} = 1 ]]; then exit 1; fi
|
||||
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
|
||||
assert 'installer runtime staged:' in built.stdout and 'scanned=0 cached=' in built.stdout, built.stdout
|
||||
assert 'verified installer runtime:' not in 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()
|
||||
|
||||
Reference in new issue
Block a user