This runbook covers the supported Windows build, test, and verification workflow for QuickViewer.
Supported Windows verification uses 64-bit Windows with Qt 6.11.2 for MSVC 2022. QuickViewer supports Windows 10 version 1809 (build 17763) and later, and the build asks the Windows headers for that version; the CI runners are newer than that floor. The script defaults are:
| Setting | Default | Allowed value |
|---|---|---|
| configuration | none | debug or release |
QV_QT_DIR |
C:\Qt\6.11.2\msvc2022_64 |
Qt MSVC kit directory |
QV_VCVARS |
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat |
full path to vcvars64.bat |
QV_BUILD_DIR |
C:\build\quickviewer-msvc2022_64-debug or C:\build\quickviewer-msvc2022_64-release |
any writable build-tree path |
QV_JOM |
C:\Qt\Tools\QtCreator\bin\jom\jom.exe |
full path to jom.exe |
QV_JOBS |
unset | positive integer; passed as jom -j N |
QV_HEIF_SOURCE |
..\..\qt-heic-image-plugin relative to the repository |
qt-heic-image-plugin source/build root |
QV_JOBS is optional. When it is unset, jom is invoked without -j and
uses its normal automatic parallelism. The script does not fall back to
nmake when jom is missing.
The SVG loader also requires Rust 1.87.0 or newer with Cargo. A standard Windows rustup installation is detected automatically by the project files.
Each build directory is configured as a single qmake configuration. The Debug tree contains Debug artifacts and test targets. The Release tree contains Release application/library targets; ordinary Release qmake generation omits test targets that are not executed by the Release workflow.
scripts\verify-windows.cmd is the single entry point for supported Windows
build, test, and verification work.
Normal builds are incremental. If the required Makefile already exists, the
script goes directly to jom. It runs qmake -r only when the Makefile is
missing or when --qmake is explicitly requested.
There is no --rebuild or other automatic clean/delete mode. The script never
deletes or recreates the Debug or Release build directory. If a genuinely clean
tree is required, delete or move that build directory manually and then run the
desired command again.
Automated unit and regression tests run in Debug. Debug is the primary development configuration because it uses the MSVC debug runtime and iterator checks. Release is verified separately for release-specific qmake branches, optimized compile/link behavior, and deployment readiness; ordinary Release builds do not duplicate the Debug test targets or suite.
Use the narrowest build and test mode that covers the change during development.
Run the normal debug command when complete Debug verification is required, and
run the normal release command when Release compile/link verification is
required.
| Command | Build scope | qmake behavior | Staging | Tests |
|---|---|---|---|---|
verify-windows.cmd debug |
top-level incremental Debug build | only if Makefile is missing | translations + Debug HEIF support | normal Debug suite |
verify-windows.cmd release |
top-level incremental Release build | only if Makefile is missing | translations + Release HEIF support | none |
... --qmake |
same build scope as the selected build mode | force qmake -r first |
unchanged | unchanged |
... --build-only |
normal top-level incremental build | normal/--qmake rules |
normal usable-build staging | none |
... --build-viewer-only |
only apps\quickviewer |
normal/--qmake rules |
translations + matching HEIF support | none |
debug --tests-only |
no build | no qmake | Debug HEIF support | normal Debug suite |
debug --test <name> [test-function] |
no build | no qmake | Debug HEIF support | one selected test |
--qmake is an independent modifier for build modes and can be combined with
--build-only or --build-viewer-only. It is intentionally rejected with
--tests-only and --test, because those modes do not build anything.
--build-viewer-only is the fastest narrow edit/build path. It runs jom only
inside apps\quickviewer; it may therefore leave changed libraries or other
dependencies stale. Use the normal top-level build for changes outside the
QuickViewer application itself.
Normal Debug verification:
scripts\verify-windows.cmd debugNormal Release build and staging:
scripts\verify-windows.cmd releaseForce qmake regeneration before either build:
scripts\verify-windows.cmd debug --qmake
scripts\verify-windows.cmd release --qmakeBuild without running tests:
scripts\verify-windows.cmd debug --build-only
scripts\verify-windows.cmd release --build-onlyFast QuickViewer-only build:
scripts\verify-windows.cmd debug --build-viewer-onlyRun tests from existing Debug artifacts without rebuilding:
scripts\verify-windows.cmd debug --tests-onlyIf a required test executable is missing, test-only modes fail rather than silently skipping it.
Use one stable test name instead of a dedicated option for each test target:
scripts\verify-windows.cmd debug --test windowstartup
scripts\verify-windows.cmd debug --test viewernavigation
scripts\verify-windows.cmd debug --test windowstartup SomeTestFunctionSupported names are:
| Test name | Executable |
|---|---|
prefetchplanner |
tst_prefetchplannertest.exe |
asynccache |
tst_asynccachetest.exe |
latestresultdispatcher |
tst_latestresultdispatchertest.exe |
fileloader |
tst_fileloadertest.exe |
svgloader |
tst_svgloadertest.exe |
viewernavigation |
tst_viewernavigationtest.exe |
windowstartup |
tst_windowstartuptest.exe |
catalogdatabase |
tst_catalogdatabasetest.exe |
The optional test-function is passed to QtTest as the function selector.
--viewer-only and --startup-only are obsolete and are not supported.
Build and test output is far larger than a conversation needs. When an agent runs any of the commands above, redirect stdout and stderr to a temporary file outside the repository and search that file instead of streaming the whole run. The exact command is in "Full Debug verification" for a Windows command prompt and in "Running from WSL" for a WSL shell; both already write to a log file and search it rather than printing the run in full.
Keep these rules when adapting those commands:
- Write the log outside the source tree (a WSL temporary directory, or
C:\buildon Windows) so it cannot be committed. Do not commit verification logs, transcripts, or captured test reports. - Save the exit code immediately after the command, before the search runs,
because the search replaces
ERRORLEVEL/$?. The saved exit code is authoritative; a matchingTotals:line alone does not prove success. - Search for the error markers listed in those examples and print only the matching lines, with a little surrounding context for the first failure.
- Keep the log until the outcome is understood and anything worth reporting has been captured, then delete it.
From a Windows command prompt in the repository root:
if not exist C:\build mkdir C:\build
call scripts\verify-windows.cmd debug > C:\build\quickviewer-test.log 2>&1
set "QV_TEST_EXIT=%ERRORLEVEL%"
findstr /n /i /c:"Totals:" /c:"All tests passed" /c:": error " /c:"fatal error" /c:"FAILED" /c:"ERROR:" C:\build\quickviewer-test.log
echo Exit code: %QV_TEST_EXIT%The saved exit code is authoritative. Inspect the log around the first error if it is nonzero.
scripts\verify-windows.cmd releaseUse this for build-system changes, Release-only code paths, or when preparing a distributable build. Release builds the application and its normal dependencies with optimization but does not compile or run the Debug test suite.
A successful build stages project runtime data and HEIF support, but it does not copy the Qt runtime next to the application. Deploy Qt explicitly when the executable must launch from Windows Explorer:
scripts\deploy-windows.cmd debug
scripts\deploy-windows.cmd releaseDeployment is deliberately separate because Qt DLLs do not need to be recopied after every source-only rebuild.
Keep the source tree on the WSL filesystem and build trees on the Windows filesystem:
repo_win_path="$(wslpath -w "$PWD")"
mkdir -p /mnt/c/build
(
cd /mnt/c
cmd.exe /d /c pushd "$repo_win_path" '&&' call \
'scripts\verify-windows.cmd' debug \
'>' 'C:\build\quickviewer-test.log' '2>&1'
)
build_status=$?
rg -n -i 'Totals:|All tests passed|: error |fatal error|FAILED|ERROR:' \
/mnt/c/build/quickviewer-test.log
printf 'Exit code: %s\n' "$build_status"For Release, replace the command with
scripts\verify-windows.cmd release.
--qmake regenerates the Makefiles with whatever path the script was invoked
through. The pushd above maps a drive letter for the UNC path first, so it is
safe; invoking scripts\verify-windows.cmd ... --qmake directly through
\\wsl.localhost\... records UNC source paths instead, and the next build then
fails with Error: dependent '\\wsl.localhost\...' does not exist. Use the
pushd form or a mapped drive path (for example Z:\...).
Image loading and decoder performance should be measured with a Release build. See Benchmark.md for the benchmark suites, decoder comparison conditions, and output format.
set "QV_PROFILE_FIRST_IMAGE=C:\build\qv-first-image.tsv"
start "" /wait "C:\build\quickviewer-msvc2022_64-release\bin\QuickViewer.exe" "C:\path\book.rar"
set "QV_PROFILE_FIRST_IMAGE="QV_PROFILE_FOLDER_TEXT=1 extends QV_PROFILE_FIRST_IMAGE profiling past the
reveal until the folder list has its text. It settles the whole folder, not only
the rows on screen: folder-text.gui-heartbeat counts event-loop service while
that happens, folder-text.profile-timeout means the run never settled, and
folder-text.idle-stop marks the text helper exiting after its idle period.
set "QV_PROFILE_FIRST_IMAGE=C:\build\qv-folder-text.tsv"
set "QV_PROFILE_FOLDER_TEXT=1"
start "" /wait "C:\build\quickviewer-msvc2022_64-release\bin\QuickViewer.exe" "C:\path\book.zip"
set "QV_PROFILE_FOLDER_TEXT="
set "QV_PROFILE_FIRST_IMAGE="The helper is the same executable in --folder-text-helper mode, so nothing
extra is deployed. Check a folder holding a name the UI font cannot draw: it
shows a placeholder first, then the real text once the helper answers. While it
loads, close and reopen the panel, scroll and select rows, and try a narrow
column, a fullscreen window and another screen DPI.
scripts\verify-windows.cmd debug --test windowstartup covers the model, cache
and transport cases.
C++ formatting is enforced by the tracked pre-commit hook and by CI. See CppLint.md for the hook mechanics, the lint scope, the commands to check or apply formatting manually, and the environment requirements.
Startup painting, fullscreen, input timing, and other visual behavior must also be checked interactively on Windows when affected. Headless automation does not establish visual correctness.
A check that temporarily edits the portable quickviewer.ini next to the
executable - it holds the history of opened volumes - must keep that file
byte-exact: it is UTF-8, while PowerShell's Get-Content and Set-Content use
the system ANSI code page by default, and one read/write round trip through
them corrupts every name that code page cannot represent, which leaves those
history entries unopenable. Copy the bytes with [IO.File]::ReadAllBytes and
WriteAllBytes, and read and write text with [IO.File]::ReadAllText and
WriteAllText plus New-Object System.Text.UTF8Encoding($false).
verify-windows.cmd regenerates Makefiles only when they are missing or when
--qmake is passed. Adding or removing a source in a .pro therefore leaves an
existing build tree with a stale object list, and the first symptom is a link
error about a class that clearly exists in the tree:
volume.obj : error LNK2001: unresolved external symbol
"public: __cdecl ImageDecoder::ImageDecoder(struct ImageDecodeSettings)"
fatal error LNK1120: 7 unresolved externals
Pass --qmake for the configuration you are building. Debug and Release keep
separate build trees, so forcing qmake in one does not regenerate the other.
A first --qmake build can still link against the old object list. jom may
regenerate a sub-Makefile while it is already using the dependency graph it
read at startup, so that pass links without the new object. verify-windows.cmd
handles both ends of that: --qmake also runs the qmake_all target, which
regenerates every sub-Makefile before the build starts, and a failed build
regenerates them and retries once. A single invocation is therefore enough, and
a failure that survives the retry is a real one.
Qt 6.11.2 moc can fail to parse a translation unit that contains an awkward
multi-line raw string literal before it has finished parsing the Q_OBJECT
class. It then writes a zero-byte .moc and still exits 0:
tst_viewernavigationtest.cpp: note: No relevant classes found. No output generated.
The compiler accepts the empty file, so the first visible failure is a link error about the meta-object:
tst_viewernavigationtest.obj : error LNK2001: unresolved external symbol
"public: virtual struct QMetaObject const * __cdecl ViewerNavigationTest::metaObject(void)const"
The message text is localized, so match on the symbol name. When a link error
names metaObject, qt_metacast, or qt_metacall, check the size of the
generated .moc before anything else. In the test file that triggered this,
the cause was an inline SVG literal inside a slot:
const QByteArray svg = R"(<svg xmlns="http://www.w3.org/2000/svg" width="40" height="20">
<rect width="40" height="20" fill="#4080c0"/>
</svg>)";An escaped literal decodes identically and parses cleanly:
const QByteArray svg =
"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"20\">"
"<rect width=\"40\" height=\"20\" fill=\"#4080c0\"/>"
"</svg>";The exact minimal trigger is not fully characterized. Verified so far: the same
literal is harmless when it appears after the Q_OBJECT class, a multi-line
raw string without double quotes is harmless, and a single-line raw string
containing // is harmless. Prefer escaped literals in any file that moc
processes, or move the data out of the class body.
tests/windowstartup and tests/languageswitch include
apps/quickviewer/QuickViewer.pro directly, so a source added to the
application reaches those two without an edit of their own.
tests/viewernavigation lists application sources explicitly and needs its own
entry.