Skip to content

#14584 Stop Windows build race copying Qt DLLs into the shared build root - #14585

Merged
magnesj merged 2 commits into
OPM:devfrom
magnesj:14584-extract-projectfile-versions-output-dir
Aug 22, 2026
Merged

#14584 Stop Windows build race copying Qt DLLs into the shared build root#14585
magnesj merged 2 commits into
OPM:devfrom
magnesj:14584-extract-projectfile-versions-output-dir

Conversation

@magnesj

@magnesj magnesj commented Aug 22, 2026

Copy link
Copy Markdown
Member

Fixes #14584

Problem

extract-projectfile-versions links into the shared build root and copies Qt6Core.dll and Qt6Sql.dll next to its executable as a POST_BUILD step. The generated_classes.py edge runs ResInsight.exe out of that same directory with those DLLs loaded, and nothing orders the two edges relative to each other, so Ninja is free to schedule them concurrently. Overwriting a mapped DLL is a sharing violation on Windows, which fails the copy and takes the build down.

Fix

Give the target its own RUNTIME_OUTPUT_DIRECTORY. It is a standalone developer tool with no reason to share the application output directory, so moving it removes the conflict rather than narrowing it. The copy also becomes copy_if_different, matching the equivalent step in the unit test target.

Ordering the two edges with a dependency was considered. It closes this specific race, but it is one hand-written edge per racer, it puts an unrelated ThirdParty tool on the critical path to the Python bindings, and nothing stops the next target that lands in the build root from reintroducing the same bug.

ResInsight-tests

While fixing the reported target, ResInsight-tests turned out to be in the same position: CMakeLists.txt:62 points CMAKE_RUNTIME_OUTPUT_DIRECTORY at the build root for everything, and the test target copies its full runtime DLL set there with the same unordered POST_BUILD pattern. That set is a superset of the one that failed, covering Qt6Gui, Qt6Widgets, arrow, protobuf, abseil and the rest. It fires rarely only because copy_if_different compares content, so windeployqt having already placed identical DLLs makes it a no-op — on a cold build where the copy wins that race it genuinely writes.

It is moved out of the build root the same way. The companion PreBuildFileCopyTest target hardcoded ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}, so it is pointed at the new directory as well to keep the ODB, OpenVDS and HDF5 runtime files next to the executable; it runs before the link creates that directory, so the directory is created first.

The three workflows that run the test executable by path are updated: ResInsightWithCache.yml (three matrix entries), ResInsightMac.yml and rhel8-unit-tests.yml. Dockerfile.rhel8 and the rhel8 build step reference only the target name and are unaffected.

Install packages are unchanged

Packaging on every platform goes through cmake --build --target install. Diffing every generated cmake_install.cmake in the tree from pristine dev to this branch, excluding FetchContent _deps/*-subbuild scaffolding, the entire diff is one line — the source path of the tool executable. The DESTINATION is identical, since install(TARGETS ...) tracks RUNTIME_OUTPUT_DIRECTORY automatically. ResInsight-tests has no install rule and contributes nothing.

Qt6Sql.dll was checked specifically, since losing it from the package would have been a real regression: RiaProjectBackupTools.cpp uses QSqlDatabase and both ApplicationExeCode and ApplicationLibCode link Qt6::Sql, so it is a genuine ResInsight.exe dependency deployed by qt_generate_deploy_app_script(TARGET ResInsight) and by RUNTIME_DEPENDENCIES on Windows — never by the copy being moved here.

Verification

Verified locally on Windows (MSVC 19.51, Ninja, Qt 6.10.1):

  • Both targets build; executables and their runtime DLLs land in the new directories.
  • extract-projectfile-versions.exe still runs from its new location.
  • Full unit test suite run from the new location: 1056 tests in 185 suites, all passed. No missing DLLs and no Qt platform plugin problem — RiaConsoleApplication derives from QCoreApplication, so no GUI plugin is required — and test data resolves through the absolute paths baked into RiaTestDataDirectory.h.cmake.
  • cmake-format run with cmake/cmake-format.py on the ApplicationLibCode file covered by the format workflow.

Not verified locally: the Linux and macOS legs. They move the test binary too, and while RI_FILENAMES there is the OpenVDS shared libraries plus SEGYImport, all copied along to the new directory, shared-library resolution on those platforms goes through RPATH rather than the executable directory. CI is the real confirmation.

One cosmetic note for existing build trees: a stale extract-projectfile-versions.exe and ResInsight-tests remain in the build root, since Ninja does not remove outputs of retired edges. Harmless, cleared by ninja -t cleandead or a fresh configure directory.

extract-projectfile-versions linked into the shared build root and copied
Qt6Core.dll and Qt6Sql.dll next to its executable as a POST_BUILD step. The
generated_classes.py edge runs ResInsight.exe out of that same directory with
those DLLs loaded, and nothing orders the two edges relative to each other, so
Ninja was free to schedule them concurrently. Overwriting a mapped DLL is a
sharing violation on Windows, which failed the copy and took the build down.

Give the target its own RUNTIME_OUTPUT_DIRECTORY. It is a standalone developer
tool with no reason to share the application output directory, so moving it
removes the conflict instead of narrowing it. Also switch the copy to
copy_if_different, matching the equivalent step in the unit test target.
ResInsight-tests linked into the build root and copied its full runtime DLL
set there as a POST_BUILD step, the same pattern that made
extract-projectfile-versions race against the generated_classes.py edge. The
set is a superset of the one that failed, covering Qt6Gui, Qt6Widgets and the
rest, and nothing orders it against the code generation edge either. It fires
rarely only because copy_if_different compares content, so windeployqt having
already placed identical DLLs makes it a no-op. On a cold build where the copy
wins that race it genuinely writes.

Give the target its own RUNTIME_OUTPUT_DIRECTORY, and point the companion
PreBuildFileCopyTest target at the same directory so the ODB, OpenVDS and HDF5
runtime files stay next to the executable. That target runs before the link
creates the directory, so create it first.

Update the three workflows that run the test executable by path.
@magnesj
magnesj merged commit c6c5ff5 into OPM:dev Aug 22, 2026
9 of 10 checks passed
@magnesj
magnesj deleted the 14584-extract-projectfile-versions-output-dir branch August 22, 2026 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: build fails intermittently copying Qt DLLs while ResInsight.exe is running for Python code generation

1 participant