instrumentation: Reset queries on repeat configure steps from CMake GUI

Prevent repeat configure calls from the CMake GUI or `ccmake` tool from
loading query duplicates, which caused many duplicate callbacks to be
invoked on each subsequent hook.

Also ensure that multiple subsequent configure steps invoked by CMake
GUI and `ccmake` each have an associated snippet file during generate.

Fixes: #27651
Co-Authored-By: Tyler Yankee <tyler.yankee@kitware.com>
This commit is contained in:
Martin Duffy
2026-03-04 16:22:47 -05:00
committed by Tyler Yankee
co-authored by Tyler Yankee
parent 1a63a737c4
commit fb541168f0
13 changed files with 179 additions and 9 deletions
+12 -1
View File
@@ -338,6 +338,15 @@ following:
These files remain in the build tree until after `Indexing`_ occurs and any
user-specified `Callbacks`_ are executed.
.. note::
Configure and generate snippet files are not written by CMake until the
generate step is complete. When using :manual:`cmake-gui(1)` or
:manual:`ccmake(1)`, triggering only configure step(s) without generating the
project files will not generate any configure snippets. Once the generate
step is run, there will be one configure snippet for each time the configure
step was run.
Snippet files have a filename with the syntax
``<role>-<hash>-<timestamp>.json`` and contain the following data:
@@ -433,7 +442,9 @@ Snippet files have a filename with the syntax
``cmakeContent``
The path to a `v1 CMake Content File`_ located under ``data``, which
contains information about the CMake configure and generate steps
responsible for generating the ``command`` in this snippet.
responsible for generating the ``command`` in this snippet. When using
:manual:`cmake-gui(1)` or :manual:`ccmake(1)`, this field may be ``null``
for all configure steps up to the most recent one before the generate step.
``showOnly``
A boolean representing whether the
+24 -5
View File
@@ -4,6 +4,7 @@
#include <chrono>
#include <ctime>
#include <iomanip>
#include <iterator>
#include <set>
#include <sstream>
#include <stdexcept>
@@ -108,6 +109,7 @@ cmInstrumentation::cmInstrumentation(std::string const& binary_dir,
void cmInstrumentation::LoadQueries()
{
this->ResetQueries();
auto const readJSONQueries = [this](std::string const& dir) {
if (cmSystemTools::FileIsDirectory(dir) && this->ReadJSONQueries(dir)) {
this->hasQuery = true;
@@ -120,6 +122,16 @@ void cmInstrumentation::LoadQueries()
}
}
void cmInstrumentation::ResetQueries()
{
this->hasQuery = false;
this->options.clear();
this->hooks.clear();
this->callbacks.clear();
this->queryFiles.clear();
this->errorMsg.clear();
}
void cmInstrumentation::CheckCDashVariable()
{
std::string envVal;
@@ -311,6 +323,7 @@ void cmInstrumentation::ClearGeneratedQueries()
if (cmSystemTools::FileIsDirectory(dir)) {
cmSystemTools::RemoveADirectory(dir);
}
this->writtenJsonQueries = 0;
}
bool cmInstrumentation::HasQuery() const
@@ -730,15 +743,21 @@ int cmInstrumentation::InstrumentCommand(
// Don't write configure snippet until generate time
if (command_type == "configure") {
this->configureSnippetData = root;
this->configureSnippetName = file_name;
this->configureSnippetData[file_name] = root;
} else {
// Add reference to CMake content and write out configure snippet after
// generate
if (command_type == "generate") {
addCMakeContent(this->configureSnippetData);
this->WriteInstrumentationJson(this->configureSnippetData, "data",
this->configureSnippetName);
for (auto it = this->configureSnippetData.begin();
it != this->configureSnippetData.end(); ++it) {
if (std::next(it) != this->configureSnippetData.end()) {
it->second["cmakeContent"] = Json::nullValue;
} else {
addCMakeContent(it->second);
}
this->WriteInstrumentationJson(it->second, "data", it->first);
}
this->configureSnippetData.clear();
}
this->WriteInstrumentationJson(root, "data", file_name);
}
+2 -2
View File
@@ -111,6 +111,7 @@ private:
Json::Value const& snippetData);
size_t AssignTargetToTraceThread(std::vector<uint64_t>& workers,
uint64_t timeStart, uint64_t duration);
void ResetQueries();
std::string binaryDir;
std::string timingDirv1;
std::string userTimingDirv1;
@@ -127,8 +128,7 @@ private:
bool ranSystemChecks = false;
bool ranOSCheck = false;
Json::Value customContent = Json::objectValue;
Json::Value configureSnippetData;
std::string configureSnippetName;
std::map<std::string, Json::Value> configureSnippetData;
#ifndef CMAKE_BOOTSTRAP
std::unique_ptr<cmsys::SystemInformation> systemInformation;
cmsys::SystemInformation& GetSystemInformation();
+1 -1
View File
@@ -2701,6 +2701,7 @@ int cmake::ActualConfigure()
this->ConfigureLog = cm::make_unique<cmConfigureLog>(
cmStrCat(this->GetHomeOutputDirectory(), "/CMakeFiles"_s),
this->FileAPI->GetConfigureLogVersions());
this->Instrumentation->ClearGeneratedQueries();
this->Instrumentation->CheckCDashVariable();
}
#endif
@@ -2968,7 +2969,6 @@ void cmake::InitializeInstrumentation()
this->Instrumentation = cm::make_unique<cmInstrumentation>(
this->State->GetBinaryDirectory(),
cmInstrumentation::LoadQueriesAfter::No);
this->Instrumentation->ClearGeneratedQueries();
}
#endif
}
+20
View File
@@ -34,6 +34,18 @@ function(run_cmake_gui_test name)
if(EXISTS "${_cmakepresets_in}")
configure_file("${_cmakepresets_in}" "${_workdir}/src/CMakePresets.json" @ONLY)
endif()
set(_instrumentation_files
"${_srcdir}/main.cxx"
"${_srcdir}/lib.h"
"${_srcdir}/lib.cxx"
"${_srcdir}/callback.cmake"
)
foreach(_instrumentation_file IN LISTS _instrumentation_files)
if(EXISTS "${_instrumentation_file}.in")
cmake_path(GET _instrumentation_file FILENAME _instrumentation_file_name)
configure_file("${_instrumentation_file}.in" "${_workdir}/src/${_instrumentation_file_name}" @ONLY)
endif()
endforeach()
if(_rcgt_DO_CONFIGURE)
if(NOT _rcgt_GENERATOR)
set(_rcgt_GENERATOR "${CMakeGUITest_GENERATOR}")
@@ -170,3 +182,11 @@ run_cmake_gui_test(presetArg:noExist
"--preset=noExist"
)
run_cmake_gui_test(changingPresets)
if("${CMakeGUITest_GENERATOR}" MATCHES "Make|Ninja|FASTBuild")
run_cmake_gui_test(instrumentation)
set(instrumentation_build_dir
"${CMakeGUITest_BINARY_DIR}/instrumentation/build"
)
include(${CMAKE_CURRENT_LIST_DIR}/instrumentation/check-data-dir.cmake)
endif()
+37
View File
@@ -79,6 +79,28 @@ void CMakeGUITest::tryConfigure(int expectedResult, int timeout)
QCOMPARE(configureDoneSignalArguments.at(0).toInt(), expectedResult);
}
void CMakeGUITest::tryGenerate(int expectedResult, int timeout)
{
auto* cmake = this->m_window->findChild<QCMakeThread*>()->cmakeInstance();
CatchShow catchMessages;
catchMessages.setCallback<QMessageBox>([](QMessageBox* box) {
if (box->text().contains("Error in generation process")) {
box->accept();
}
});
QSignalSpy generateDoneSpy(cmake, &QCMake::generateDone);
QVERIFY(generateDoneSpy.isValid());
QMetaObject::invokeMethod(
this->m_window, [this]() { this->m_window->GenerateButton->click(); },
Qt::QueuedConnection);
QVERIFY(generateDoneSpy.wait(timeout));
QList<QVariant> generateDoneSignalArguments = generateDoneSpy.takeFirst();
QCOMPARE(generateDoneSignalArguments.at(0).toInt(), expectedResult);
}
void CMakeGUITest::sourceBinaryArgs()
{
QFETCH(QString, sourceDir);
@@ -151,6 +173,21 @@ void CMakeGUITest::simpleConfigure_data()
<< -1;
}
void CMakeGUITest::instrumentation()
{
this->m_window->SourceDirectory->setText(CMakeGUITest_BINARY_DIR
"/instrumentation/src");
this->m_window->BinaryDirectory->setCurrentText(CMakeGUITest_BINARY_DIR
"/instrumentation/build");
// Wait a bit for everything to update
loopSleep();
this->tryConfigure();
this->tryConfigure();
this->tryGenerate();
}
void CMakeGUITest::environment()
{
auto* cmake = this->m_window->findChild<QCMakeThread*>()->cmakeInstance();
+2
View File
@@ -16,12 +16,14 @@ private:
CMakeSetupDialog* m_window = nullptr;
void tryConfigure(int expectedResult = 0, int timeout = 60000);
void tryGenerate(int expectedResult = 0, int timeout = 60000);
private slots:
void sourceBinaryArgs();
void sourceBinaryArgs_data();
void simpleConfigure();
void simpleConfigure_data();
void instrumentation();
void environment();
void presetArg();
void presetArg_data();
@@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 4.3)
project(instrumentation)
add_executable(main main.cxx)
add_library(lib lib.cxx)
target_link_libraries(main lib)
cmake_instrumentation(
API_VERSION 1
DATA_VERSION 1
CALLBACK ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/callback.cmake
)
@@ -0,0 +1,2 @@
file(TOUCH "instrumentation/build/callback-count.txt")
file(APPEND "instrumentation/build/callback-count.txt" " \n")
@@ -0,0 +1,57 @@
set(instrumentation_v1 "${instrumentation_build_dir}/.cmake/instrumentation/v1")
file(GLOB_RECURSE queries LIST_DIRECTORIES false ${instrumentation_v1}/query/*)
list(LENGTH queries n_queries)
if (NOT n_queries EQUAL 1)
message(FATAL_ERROR "Expected one instrumentation query, got: ${n_queries}")
endif()
file(GLOB snippets_configure ${instrumentation_v1}/data/configure-*)
list(LENGTH snippets_configure n_snippets_configure)
if (NOT n_snippets_configure EQUAL 2)
message(FATAL_ERROR "Expected two configure snippets, got: ${n_snippets_configure}")
endif()
file(GLOB snippets_generate ${instrumentation_v1}/data/generate-*)
list(LENGTH snippets_generate n_snippets_generate)
if (NOT n_snippets_generate EQUAL 1)
message(FATAL_ERROR "Expected one generate snippet, got: ${n_snippets_generate}")
endif()
file(GLOB content ${instrumentation_v1}/data/content/*)
list(LENGTH content n_content)
if (NOT n_content EQUAL 1)
message(FATAL_ERROR "Expected one content file, got: ${n_content}")
endif()
# The earliest configure snippet should not have an associated content file,
# because we immediately re-configured, so the corresponding generation never
# occurred.
list(GET snippets_configure 0 first_configure)
file(READ "${first_configure}" configure_json)
string(JSON configure_content_file
ERROR_VARIABLE configure_content_error
GET "${configure_json}" "cmakeContent")
if (configure_content_error)
message(FATAL_ERROR "Failed to get cmakeContent from ${first_configure}: ${configure_content_error}")
endif()
if (NOT configure_content_file STREQUAL "")
message(FATAL_ERROR "Expected null for cmakeContent in ${first_configure}, got: ${configure_content_file}")
endif()
execute_process(
COMMAND
"${CMAKE_CTEST_COMMAND}" --collect-instrumentation
"${instrumentation_build_dir}"
)
if (NOT EXISTS "${instrumentation_build_dir}/callback-count.txt")
message(FATAL_ERROR "Expected a callback output file at: ${instrumentation_build_dir}/callback-count.txt")
endif()
file(STRINGS "${instrumentation_build_dir}/callback-count.txt" callback_lines)
list(LENGTH callback_lines n_callbacks)
if (NOT n_callbacks EQUAL 1)
message(FATAL_ERROR "Expected one callback execution, got: ${n_callbacks}")
endif()
@@ -0,0 +1,4 @@
int lib()
{
return 0;
}
+1
View File
@@ -0,0 +1 @@
int lib();
@@ -0,0 +1,5 @@
#include "lib.h"
int main()
{
return lib();
}