From 579bff660e495e8deef8f5df941077ee81acb788 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Sat, 12 Sep 2026 13:03:22 +0000 Subject: [PATCH] [build] Keep gtest builds consistent with the C++ modules state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The global C++ modules index lib/modules.idx (and the PCMs of modules that import a given module) is only regenerated by the modules_idx target, which is not in the dependency closure of a targeted build. After `cmake --build . --target testFoo` following a dictionary change, tests therefore ran against a stale index and stale dependent PCMs, producing spurious "Failed to load module" errors or nondeterministic segfaults (e.g. silently missing plugin classes such as the Minuit2 minimizer). ROOT_ADD_GTEST registrations are now recorded and, once all test directories have been processed in RootCTest.cmake, the test executables are made to depend on the modules_idx target. Since modules_idx depends on every ROOT module, building any gtest now first rebuilds the affected downstream PCMs and then regenerates lib/modules.idx, so targeted test builds always leave a consistent module state. The dependency is purely by target order: a fresh checkout without dictionary changes adds only a ninja closure check to a test build. Library-only targeted builds still can leave lib/modules.idx stale for interpreter-only use (`import ROOT`, root.exe -b) until the next full or modules_idx build. This change is very helpful for test development, because after changing the library to fix a given test, one can just do a targeted build of that test, triggering only a re-build of the library and that test instead of everything else that depends on the library. 🤖 Done with the help of AI --- cmake/modules/RootCTest.cmake | 15 +++++++++++++++ cmake/modules/RootMacros.cmake | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/cmake/modules/RootCTest.cmake b/cmake/modules/RootCTest.cmake index 54338cd765a18..5db33bbb287d8 100644 --- a/cmake/modules/RootCTest.cmake +++ b/cmake/modules/RootCTest.cmake @@ -51,6 +51,21 @@ foreach(d ${test_list}) endif() endforeach() +# Building a test executable must bring lib/modules.idx and all module +# artifacts (PCMs) up to date. Otherwise, a targeted build that changes a +# dictionary (e.g. `cmake --build . --target testFoo`) leaves the test running +# against a stale global module index and stale dependent PCMs, which +# manifests as modules that fail to load or as spurious segfaults. The +# modules_idx target depends on every ROOT module, so making the test +# executables depend on it ensures any dictionary change is first propagated +# through the dependent PCMs and the index is regenerated. +if(runtime_cxxmodules AND TARGET modules_idx) + get_property(modules_idx_gtests GLOBAL PROPERTY ROOT_MODULES_IDX_GTESTS) + foreach(modules_idx_gtest ${modules_idx_gtests}) + add_dependencies(${modules_idx_gtest} modules_idx) + endforeach() +endif() + # When ninja or the Microsoft generator are in use, tests that compile an executable might try # to rebuild the entire build tree. If multiple of these are invoked in parallel, ninja will # suffer from race conditions. diff --git a/cmake/modules/RootMacros.cmake b/cmake/modules/RootMacros.cmake index 87080dc7a5ba9..5ca11a2d2d679 100644 --- a/cmake/modules/RootMacros.cmake +++ b/cmake/modules/RootMacros.cmake @@ -1965,6 +1965,11 @@ function(ROOT_ADD_GTEST test_suite) # against. For example, tests in Core should link only against libCore. This could be tricky # to implement because some ROOT components create more than one library. ROOT_EXECUTABLE(${test_suite} ${source_files} LIBRARIES ${ARG_LIBRARIES}) + if(runtime_cxxmodules) + # Register the test so that the modules_idx dependency can be attached at + # the end of the top-level CMakeLists, where the modules_idx target exists. + set_property(GLOBAL APPEND PROPERTY ROOT_MODULES_IDX_GTESTS ${test_suite}) + endif() target_link_libraries(${test_suite} PRIVATE GTest::gtest GTest::gmock GTest::gtest_main GTest::gmock_main) if(TARGET ROOT::TestSupport) target_link_libraries(${test_suite} PRIVATE ROOT::TestSupport)