From c57d1752d471ae8d3735baf3bf78c71c0a888d32 Mon Sep 17 00:00:00 2001 From: Martin Duffy Date: Fri, 24 Jan 2025 09:51:43 -0500 Subject: cmUVProcessChain: Add Detached option for spawning daemons --- Source/cmUVProcessChain.cxx | 12 ++++++++++++ Source/cmUVProcessChain.h | 2 ++ 2 files changed, 14 insertions(+) diff --git a/Source/cmUVProcessChain.cxx b/Source/cmUVProcessChain.cxx index c64d9a4..2cab632 100644 --- a/Source/cmUVProcessChain.cxx +++ b/Source/cmUVProcessChain.cxx @@ -156,6 +156,12 @@ cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetWorkingDirectory( return *this; } +cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetDetached() +{ + this->Detached = true; + return *this; +} + uv_loop_t* cmUVProcessChainBuilder::GetLoop() const { return this->Loop; @@ -337,6 +343,9 @@ void cmUVProcessChain::InternalData::SpawnProcess( arguments.push_back(nullptr); options.args = const_cast(arguments.data()); options.flags = UV_PROCESS_WINDOWS_HIDE; + if (this->Builder->Detached) { + options.flags |= UV_PROCESS_DETACHED; + } #if UV_VERSION_MAJOR > 1 || \ (UV_VERSION_MAJOR == 1 && UV_VERSION_MINOR >= 48) || \ !defined(CMAKE_USE_SYSTEM_LIBUV) @@ -380,6 +389,9 @@ void cmUVProcessChain::InternalData::SpawnProcess( process.Process.spawn(*this->Loop, options, &process)) < 0) { process.Finish(); } + if (this->Builder->Detached) { + uv_unref((uv_handle_t*)process.Process); + } process.InputPipe.reset(); process.OutputPipe.reset(); } diff --git a/Source/cmUVProcessChain.h b/Source/cmUVProcessChain.h index b28a876..d21fb9e 100644 --- a/Source/cmUVProcessChain.h +++ b/Source/cmUVProcessChain.h @@ -39,6 +39,7 @@ public: cmUVProcessChainBuilder& SetExternalStream(Stream stdio, int fd); cmUVProcessChainBuilder& SetExternalStream(Stream stdio, FILE* stream); cmUVProcessChainBuilder& SetWorkingDirectory(std::string dir); + cmUVProcessChainBuilder& SetDetached(); uv_loop_t* GetLoop() const; @@ -69,6 +70,7 @@ private: std::vector Processes; std::string WorkingDirectory; bool MergedBuiltinStreams = false; + bool Detached = false; uv_loop_t* Loop = nullptr; }; -- cgit v0.12 From f62a4ab2ee67874f15e3f9cc5f05c087a49188e8 Mon Sep 17 00:00:00 2001 From: Martin Duffy Date: Thu, 30 Jan 2025 15:49:37 -0500 Subject: instrumentation: Refactor cmInstrumentation constructor and usage Creates a global cmInstrumentation pointer on the CMake Instance to prevent creating multiple instrumentation objects. --- Source/CTest/cmCTestLaunch.cxx | 4 +-- Source/cmInstrumentation.cxx | 49 +++++++++++++++---------------------- Source/cmInstrumentation.h | 21 +++++++--------- Source/cmInstrumentationCommand.cxx | 9 ++++--- Source/cmake.cxx | 21 +++++++++------- Source/cmake.h | 6 +++++ 6 files changed, 54 insertions(+), 56 deletions(-) diff --git a/Source/CTest/cmCTestLaunch.cxx b/Source/CTest/cmCTestLaunch.cxx index 2ab2976..f71d965 100644 --- a/Source/CTest/cmCTestLaunch.cxx +++ b/Source/CTest/cmCTestLaunch.cxx @@ -254,7 +254,7 @@ void cmCTestLaunch::RunChild() int cmCTestLaunch::Run() { - auto instrumenter = cmInstrumentation(this->Reporter.OptionBuildDir); + auto instrumentation = cmInstrumentation(this->Reporter.OptionBuildDir); std::map options; options["target"] = this->Reporter.OptionTargetName; options["source"] = this->Reporter.OptionSource; @@ -264,7 +264,7 @@ int cmCTestLaunch::Run() std::map arrayOptions; arrayOptions["outputs"] = this->Reporter.OptionOutput; arrayOptions["targetLabels"] = this->Reporter.OptionTargetLabels; - instrumenter.InstrumentCommand( + instrumentation.InstrumentCommand( this->Reporter.OptionCommandType, this->RealArgV, [this]() -> int { this->RunChild(); diff --git a/Source/cmInstrumentation.cxx b/Source/cmInstrumentation.cxx index d00fdd1..a919621 100644 --- a/Source/cmInstrumentation.cxx +++ b/Source/cmInstrumentation.cxx @@ -23,8 +23,7 @@ #include "cmSystemTools.h" #include "cmTimestamp.h" -cmInstrumentation::cmInstrumentation(std::string const& binary_dir, - bool clear_generated) +cmInstrumentation::cmInstrumentation(std::string const& binary_dir) { std::string const uuid = cmExperimental::DataForFeature(cmExperimental::Feature::Instrumentation) @@ -32,9 +31,6 @@ cmInstrumentation::cmInstrumentation(std::string const& binary_dir, this->binaryDir = binary_dir; this->timingDirv1 = cmStrCat(this->binaryDir, "/.cmake/instrumentation-", uuid, "/v1"); - if (clear_generated) { - this->ClearGeneratedQueries(); - } if (cm::optional configDir = cmSystemTools::GetCMakeConfigDirectory()) { this->userTimingDirv1 = @@ -57,24 +53,6 @@ void cmInstrumentation::LoadQueries() } } -cmInstrumentation::cmInstrumentation( - std::string const& binary_dir, - std::set& queries_, - std::set& hooks_, std::string& callback) -{ - this->binaryDir = binary_dir; - this->timingDirv1 = cmStrCat( - this->binaryDir, "/.cmake/instrumentation-", - cmExperimental::DataForFeature(cmExperimental::Feature::Instrumentation) - .Uuid, - "/v1"); - this->queries = queries_; - this->hooks = hooks_; - if (!callback.empty()) { - this->callbacks.push_back(callback); - } -} - bool cmInstrumentation::ReadJSONQueries(std::string const& directory) { cmsys::Directory d; @@ -99,20 +77,22 @@ void cmInstrumentation::ReadJSONQuery(std::string const& file) this->callbacks); } -void cmInstrumentation::WriteJSONQuery() +void cmInstrumentation::WriteJSONQuery( + std::set& queries_, + std::set& hooks_, std::string& callback) { Json::Value root; root["version"] = 1; root["queries"] = Json::arrayValue; - for (auto const& query : this->queries) { + for (auto const& query : queries_) { root["queries"].append(cmInstrumentationQuery::QueryString[query]); } root["hooks"] = Json::arrayValue; - for (auto const& hook : this->hooks) { + for (auto const& hook : hooks_) { root["hooks"].append(cmInstrumentationQuery::HookString[hook]); } root["callbacks"] = Json::arrayValue; - for (auto const& callback : this->callbacks) { + if (!callback.empty()) { root["callbacks"].append(callback); } cmsys::Directory d; @@ -132,16 +112,27 @@ void cmInstrumentation::ClearGeneratedQueries() } } -bool cmInstrumentation::HasQuery() +bool cmInstrumentation::HasQuery() const { return this->hasQuery; } -bool cmInstrumentation::HasQuery(cmInstrumentationQuery::Query query) +bool cmInstrumentation::HasQuery(cmInstrumentationQuery::Query query) const { return (this->queries.find(query) != this->queries.end()); } +bool cmInstrumentation::HasHook(cmInstrumentationQuery::Hook hook) const +{ + return (this->hooks.find(hook) != this->hooks.end()); +} + +bool cmInstrumentation::HasPreOrPostBuildHook() const +{ + return (this->HasHook(cmInstrumentationQuery::Hook::PreBuild) || + this->HasHook(cmInstrumentationQuery::Hook::PostBuild)); +} + int cmInstrumentation::CollectTimingData(cmInstrumentationQuery::Hook hook) { // Don't run collection if hook is disabled diff --git a/Source/cmInstrumentation.h b/Source/cmInstrumentation.h index 9578a5b..07b9e1d 100644 --- a/Source/cmInstrumentation.h +++ b/Source/cmInstrumentation.h @@ -21,14 +21,7 @@ class cmInstrumentation { public: - // Read Queries - cmInstrumentation(std::string const& binary_dir, - bool clear_generated = false); - // Create Query - cmInstrumentation(std::string const& binary_dir, - std::set& queries, - std::set& hooks, - std::string& callback); + cmInstrumentation(std::string const& binary_dir); int InstrumentCommand( std::string command_type, std::vector const& command, std::function const& callback, @@ -42,11 +35,16 @@ public: std::chrono::system_clock::time_point systemStart); void GetPreTestStats(); void LoadQueries(); - bool HasQuery(); - bool HasQuery(cmInstrumentationQuery::Query); + bool HasQuery() const; + bool HasQuery(cmInstrumentationQuery::Query) const; + bool HasHook(cmInstrumentationQuery::Hook) const; + bool HasPreOrPostBuildHook() const; bool ReadJSONQueries(std::string const& directory); void ReadJSONQuery(std::string const& file); - void WriteJSONQuery(); + void WriteJSONQuery(std::set& queries, + std::set& hooks, + std::string& callback); + void ClearGeneratedQueries(); int CollectTimingData(cmInstrumentationQuery::Hook hook); std::string errorMsg; @@ -61,7 +59,6 @@ private: static void InsertTimingData( Json::Value& root, std::chrono::steady_clock::time_point steadyStart, std::chrono::system_clock::time_point systemStart); - void ClearGeneratedQueries(); bool HasQueryFile(std::string const& file); static std::string GetCommandStr(std::vector const& args); static std::string ComputeSuffixHash(std::string const& command_str); diff --git a/Source/cmInstrumentationCommand.cxx b/Source/cmInstrumentationCommand.cxx index cb8fcaa..406ced8 100644 --- a/Source/cmInstrumentationCommand.cxx +++ b/Source/cmInstrumentationCommand.cxx @@ -18,6 +18,7 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmInstrumentationQuery.h" #include "cmMakefile.h" #include "cmStringAlgorithms.h" +#include "cmake.h" namespace { @@ -141,9 +142,9 @@ bool cmInstrumentationCommand(std::vector const& args, callback = cmStrCat(callback, arg); } - auto instrument = cmInstrumentation( - status.GetMakefile().GetHomeOutputDirectory(), queries, hooks, callback); - instrument.WriteJSONQuery(); - + status.GetMakefile() + .GetCMakeInstance() + ->GetInstrumentation() + ->WriteJSONQuery(queries, hooks, callback); return true; } diff --git a/Source/cmake.cxx b/Source/cmake.cxx index 3f2dc7f..5acd381 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -2611,21 +2611,24 @@ int cmake::ActualConfigure() cmStrCat(this->GetHomeOutputDirectory(), "/CMakeFiles"_s), this->FileAPI->GetConfigureLogVersions()); } + + this->Instrumentation = + cm::make_unique(this->State->GetBinaryDirectory()); + this->Instrumentation->ClearGeneratedQueries(); #endif // actually do the configure auto startTime = std::chrono::steady_clock::now(); #if !defined(CMAKE_BOOTSTRAP) - cmInstrumentation instrumentation(this->State->GetBinaryDirectory(), true); - if (!instrumentation.errorMsg.empty()) { - cmSystemTools::Error(instrumentation.errorMsg); + if (!this->Instrumentation->errorMsg.empty()) { + cmSystemTools::Error(this->Instrumentation->errorMsg); return 1; } std::function doConfigure = [this]() -> int { this->GlobalGenerator->Configure(); return 0; }; - int ret = instrumentation.InstrumentCommand( + int ret = this->Instrumentation->InstrumentCommand( "configure", this->cmdArgs, [doConfigure]() { return doConfigure(); }, cm::nullopt, cm::nullopt, true); if (ret != 0) { @@ -2670,8 +2673,8 @@ int cmake::ActualConfigure() } // Setup launchers for instrumentation #if !defined(CMAKE_BOOTSTRAP) - instrumentation.LoadQueries(); - if (instrumentation.HasQuery()) { + this->Instrumentation->LoadQueries(); + if (this->Instrumentation->HasQuery()) { std::string launcher; if (mf->IsOn("CTEST_USE_LAUNCHERS")) { launcher = @@ -3015,7 +3018,6 @@ int cmake::Generate() auto startTime = std::chrono::steady_clock::now(); #if !defined(CMAKE_BOOTSTRAP) auto profilingRAII = this->CreateProfilingEntry("project", "generate"); - cmInstrumentation instrumentation(this->State->GetBinaryDirectory()); std::function doGenerate = [this]() -> int { if (!this->GlobalGenerator->Compute()) { return -1; @@ -3024,7 +3026,8 @@ int cmake::Generate() return 0; }; - int ret = instrumentation.InstrumentCommand( + this->Instrumentation->LoadQueries(); + int ret = this->Instrumentation->InstrumentCommand( "generate", this->cmdArgs, [doGenerate]() { return doGenerate(); }); if (ret != 0) { return ret; @@ -3045,7 +3048,7 @@ int cmake::Generate() this->UpdateProgress(msg.str(), -1); } #if !defined(CMAKE_BOOTSTRAP) - instrumentation.CollectTimingData( + this->Instrumentation->CollectTimingData( cmInstrumentationQuery::Hook::PostGenerate); #endif if (!this->GraphVizFile.empty()) { diff --git a/Source/cmake.h b/Source/cmake.h index d59e844..49231f4 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -49,6 +49,7 @@ class cmDebuggerAdapter; class cmExternalMakefileProjectGeneratorFactory; class cmFileAPI; +class cmInstrumentation; class cmFileTimeCache; class cmGlobalGenerator; class cmMakefile; @@ -663,6 +664,10 @@ public: #if !defined(CMAKE_BOOTSTRAP) cmFileAPI* GetFileAPI() const { return this->FileAPI.get(); } + cmInstrumentation* GetInstrumentation() const + { + return this->Instrumentation.get(); + } #endif cmState* GetState() const { return this->State.get(); } @@ -816,6 +821,7 @@ private: #if !defined(CMAKE_BOOTSTRAP) std::unique_ptr VariableWatch; std::unique_ptr FileAPI; + std::unique_ptr Instrumentation; #endif std::unique_ptr State; -- cgit v0.12 From fc1d55f6a5bd9b05bd2b461b9596e8494fc041b6 Mon Sep 17 00:00:00 2001 From: Martin Duffy Date: Fri, 24 Jan 2025 09:58:34 -0500 Subject: instrumentation: Add preBuild and postBuild hooks for ninja Allows instrumentation indexing and callbacks to occur at the start or end of every `ninja` invocation. --- Help/manual/cmake-instrumentation.7.rst | 6 ++- Source/cmGlobalNinjaGenerator.cxx | 52 ++++++++++++++++++++++ Source/cmGlobalNinjaGenerator.h | 1 + Source/cmInstrumentation.cxx | 45 +++++++++++++++++++ Source/cmInstrumentation.h | 2 + Source/ctest.cxx | 12 +++++ Tests/RunCMake/Instrumentation/RunCMakeTest.cmake | 10 ++++- .../Instrumentation/check-ninja-hooks.cmake | 35 +++++++++++++++ Tests/RunCMake/Instrumentation/hook.cmake | 4 ++ .../query/cmake-command-ninja.cmake | 6 +++ 10 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 Tests/RunCMake/Instrumentation/check-ninja-hooks.cmake create mode 100644 Tests/RunCMake/Instrumentation/query/cmake-command-ninja.cmake diff --git a/Help/manual/cmake-instrumentation.7.rst b/Help/manual/cmake-instrumentation.7.rst index 60db156..da9c0ff 100644 --- a/Help/manual/cmake-instrumentation.7.rst +++ b/Help/manual/cmake-instrumentation.7.rst @@ -117,8 +117,10 @@ optional. should be one of the following: * ``postGenerate`` - * ``preCMakeBuild`` - * ``postCMakeBuild`` + * ``preBuild`` (:ref:`Ninja Generators`. only, when ``ninja`` is invoked) + * ``postBuild`` (:ref:`Ninja Generators`. only, when ``ninja`` completes) + * ``preCMakeBuild`` (when ``cmake --build`` is invoked) + * ``postCMakeBuild`` (when ``cmake --build`` completes) * ``postInstall`` * ``postTest`` diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx index bf2da7d..e3eee6a 100644 --- a/Source/cmGlobalNinjaGenerator.cxx +++ b/Source/cmGlobalNinjaGenerator.cxx @@ -32,6 +32,7 @@ #include "cmGeneratedFileStream.h" #include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" +#include "cmInstrumentation.h" #include "cmLinkLineComputer.h" #include "cmList.h" #include "cmListFileCache.h" @@ -1761,6 +1762,13 @@ void cmGlobalNinjaGenerator::WriteBuiltinTargets(std::ostream& os) this->WriteTargetRebuildManifest(os); this->WriteTargetClean(os); this->WriteTargetHelp(os); +#if !defined(CMAKE_BOOTSTRAP) + if (this->GetCMakeInstance() + ->GetInstrumentation() + ->HasPreOrPostBuildHook()) { + this->WriteTargetInstrument(os); + } +#endif for (std::string const& config : this->GetConfigNames()) { this->WriteTargetDefault(*this->GetConfigFileStream(config)); @@ -1835,6 +1843,14 @@ void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os) } reBuild.ImplicitDeps.push_back(this->CMakeCacheFile); +#if !defined(CMAKE_BOOTSTRAP) + if (this->GetCMakeInstance() + ->GetInstrumentation() + ->HasPreOrPostBuildHook()) { + reBuild.ExplicitDeps.push_back(this->NinjaOutputPath("start_instrument")); + } +#endif + // Use 'console' pool to get non buffered output of the CMake re-run call // Available since Ninja 1.5 if (this->SupportsDirectConsole()) { @@ -2180,6 +2196,42 @@ void cmGlobalNinjaGenerator::WriteTargetHelp(std::ostream& os) } } +void cmGlobalNinjaGenerator::WriteTargetInstrument(std::ostream& os) +{ + // Write rule + { + cmNinjaRule rule("START_INSTRUMENT"); + rule.Command = cmStrCat( + "\"", cmSystemTools::GetCTestCommand(), "\" --start-instrumentation \"", + this->GetCMakeInstance()->GetHomeOutputDirectory(), "\""); +#ifndef _WIN32 + /* + * On Unix systems, Ninja will prefix the command with `/bin/sh -c`. + * Use exec so that Ninja is the parent process of the command. + */ + rule.Command = cmStrCat("exec ", rule.Command); +#endif + rule.Description = "Collecting build metrics"; + rule.Comment = "Rule to initialize instrumentation daemon."; + rule.Restat = "1"; + WriteRule(*this->RulesFileStream, rule); + } + + // Write build + { + cmNinjaBuild phony("phony"); + phony.Comment = "Phony target to keep START_INSTRUMENTATION out of date."; + phony.Outputs.push_back(this->NinjaOutputPath("CMakeFiles/instrument")); + cmNinjaBuild instrument("START_INSTRUMENT"); + instrument.Comment = "Start instrumentation daemon."; + instrument.Outputs.push_back(this->NinjaOutputPath("start_instrument")); + instrument.ExplicitDeps.push_back( + this->NinjaOutputPath("CMakeFiles/instrument")); + WriteBuild(os, phony); + WriteBuild(os, instrument); + } +} + void cmGlobalNinjaGenerator::InitOutputPathPrefix() { this->OutputPathPrefix = diff --git a/Source/cmGlobalNinjaGenerator.h b/Source/cmGlobalNinjaGenerator.h index 5ccb314..acc9ee4 100644 --- a/Source/cmGlobalNinjaGenerator.h +++ b/Source/cmGlobalNinjaGenerator.h @@ -536,6 +536,7 @@ private: void WriteTargetRebuildManifest(std::ostream& os); bool WriteTargetCleanAdditional(std::ostream& os); void WriteTargetClean(std::ostream& os); + void WriteTargetInstrument(std::ostream& os); void WriteTargetHelp(std::ostream& os); void ComputeTargetDependsClosure( diff --git a/Source/cmInstrumentation.cxx b/Source/cmInstrumentation.cxx index a919621..245195f 100644 --- a/Source/cmInstrumentation.cxx +++ b/Source/cmInstrumentation.cxx @@ -11,6 +11,7 @@ #include #include +#include #include "cmsys/Directory.hxx" #include "cmsys/FStream.hxx" @@ -22,6 +23,7 @@ #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmTimestamp.h" +#include "cmUVProcessChain.h" cmInstrumentation::cmInstrumentation(std::string const& binary_dir) { @@ -485,3 +487,46 @@ std::string cmInstrumentation::ComputeSuffixTime() << std::setfill('0') << std::setw(4) << tms; return ss.str(); } + +/* + * Called by ctest --start-instrumentation as part of the START_INSTRUMENTATION + * rule when using the Ninja generator. + * This creates a detached process which waits for the Ninja process to die + * before running the postBuild hook. In this way, the postBuild hook triggers + * after every ninja invocation, regardless of whether the build passed or + * failed. + */ +int cmInstrumentation::SpawnBuildDaemon() +{ + // preBuild Hook + this->CollectTimingData(cmInstrumentationQuery::Hook::PreBuild); + + // postBuild Hook + if (this->HasHook(cmInstrumentationQuery::Hook::PostBuild)) { + auto ninja_pid = uv_os_getppid(); + if (ninja_pid) { + std::vector args; + args.push_back(cmSystemTools::GetCTestCommand()); + args.push_back("--wait-and-collect-instrumentation"); + args.push_back(this->binaryDir); + args.push_back(std::to_string(ninja_pid)); + auto builder = cmUVProcessChainBuilder().SetDetached().AddCommand(args); + auto chain = builder.Start(); + uv_run(&chain.GetLoop(), UV_RUN_DEFAULT); + } + } + return 0; +} + +/* + * Always called by ctest --wait-and-collect-instrumentation in a detached + * process. Waits for the given PID to end before running the postBuild hook. + * + * See SpawnBuildDaemon() + */ +int cmInstrumentation::CollectTimingAfterBuild(int ppid) +{ + while (0 == uv_kill(ppid, 0)) { + }; + return this->CollectTimingData(cmInstrumentationQuery::Hook::PostBuild); +} diff --git a/Source/cmInstrumentation.h b/Source/cmInstrumentation.h index 07b9e1d..e9eceac 100644 --- a/Source/cmInstrumentation.h +++ b/Source/cmInstrumentation.h @@ -46,6 +46,8 @@ public: std::string& callback); void ClearGeneratedQueries(); int CollectTimingData(cmInstrumentationQuery::Hook hook); + int SpawnBuildDaemon(); + int CollectTimingAfterBuild(int ppid); std::string errorMsg; private: diff --git a/Source/ctest.cxx b/Source/ctest.cxx index d253d5b..ad82daa 100644 --- a/Source/ctest.cxx +++ b/Source/ctest.cxx @@ -189,6 +189,18 @@ int main(int argc, char const* const* argv) return cmCTestLaunch::Main(argc, argv, cmCTestLaunch::Op::Instrument); } + // Dispatch post-build instrumentation daemon for ninja + if (argc == 3 && strcmp(argv[1], "--start-instrumentation") == 0) { + return cmInstrumentation(argv[2]).SpawnBuildDaemon(); + } + + // Dispatch 'ctest --collect-instrumentation' once given PID finishes + if (argc == 4 && + strcmp(argv[1], "--wait-and-collect-instrumentation") == 0) { + return cmInstrumentation(argv[2]).CollectTimingAfterBuild( + std::stoi(argv[3])); + } + // Dispatch 'ctest --collect-instrumentation' mode directly. if (argc == 3 && strcmp(argv[1], "--collect-instrumentation") == 0) { return cmInstrumentation(argv[2]).CollectTimingData( diff --git a/Tests/RunCMake/Instrumentation/RunCMakeTest.cmake b/Tests/RunCMake/Instrumentation/RunCMakeTest.cmake index f7dc4eb..2b70972 100644 --- a/Tests/RunCMake/Instrumentation/RunCMakeTest.cmake +++ b/Tests/RunCMake/Instrumentation/RunCMakeTest.cmake @@ -6,7 +6,7 @@ function(instrument test) set(config "${CMAKE_CURRENT_LIST_DIR}/config") set(ENV{CMAKE_CONFIG_DIR} ${config}) cmake_parse_arguments(ARGS - "BUILD;INSTALL;TEST;COPY_QUERIES;NO_WARN;STATIC_QUERY;DYNAMIC_QUERY;INSTALL_PARALLEL;MANUAL_HOOK" + "BUILD;BUILD_MAKE_PROGRAM;INSTALL;TEST;COPY_QUERIES;NO_WARN;STATIC_QUERY;DYNAMIC_QUERY;INSTALL_PARALLEL;MANUAL_HOOK" "CHECK_SCRIPT;CONFIGURE_ARG" "" ${ARGN}) set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}) set(uuid "a37d1069-1972-4901-b9c9-f194aaf2b6e0") @@ -58,6 +58,9 @@ function(instrument test) if (ARGS_BUILD) run_cmake_command(${test}-build ${CMAKE_COMMAND} --build . --config Debug) endif() + if (ARGS_BUILD_MAKE_PROGRAM) + run_cmake_command(${test}-make-program ${RunCMake_MAKE_PROGRAM}) + endif() if (ARGS_INSTALL) run_cmake_command(${test}-install ${CMAKE_COMMAND} --install . --prefix install --config Debug) endif() @@ -112,3 +115,8 @@ instrument(cmake-command-bad-arg NO_WARN) instrument(cmake-command-parallel-install BUILD INSTALL TEST NO_WARN INSTALL_PARALLEL DYNAMIC_QUERY CHECK_SCRIPT check-data-dir.cmake) +if (UNIX AND ${RunCMake_GENERATOR} MATCHES "^Ninja") + instrument(cmake-command-ninja NO_WARN + BUILD_MAKE_PROGRAM + CHECK_SCRIPT check-ninja-hooks.cmake) +endif() diff --git a/Tests/RunCMake/Instrumentation/check-ninja-hooks.cmake b/Tests/RunCMake/Instrumentation/check-ninja-hooks.cmake new file mode 100644 index 0000000..60b2f7b --- /dev/null +++ b/Tests/RunCMake/Instrumentation/check-ninja-hooks.cmake @@ -0,0 +1,35 @@ +set(NUM_TRIES 30) +set(DELAY 1) + +if (NOT EXISTS ${v1}/preBuild.hook) + set(RunCMake_TEST_FAILED "preBuild hook did not run\n") +endif() + +macro(hasPostBuildArtifacts) + if (NOT postBuildRan AND EXISTS ${v1}/postBuild.hook) + set(postBuildRan 1) + endif() + if (NOT dataDirClean) + file(GLOB snippets "${v1}/data/*") + if ("${snippets}" STREQUAL "") + set(dataDirClean 1) + endif() + endif() +endmacro() + +set(postBuildRan 0) +set(dataDirClean 0) +foreach(_ RANGE ${NUM_TRIES}) + hasPostBuildArtifacts() + if (postBuildRan AND dataDirClean) + break() + endif() + execute_process(COMMAND ${CMAKE_COMMAND} -E sleep ${DELAY}) +endforeach() + +if (NOT postBuildRan) + string(APPEND RunCMake_TEST_FAILED "postBuild hook did not run\n") +endif() +if (NOT dataDirClean) + string(APPEND RunCMake_TEST_FAILED "Snippet files not fully removed post build\n") +endif() diff --git a/Tests/RunCMake/Instrumentation/hook.cmake b/Tests/RunCMake/Instrumentation/hook.cmake index 973e7d8..f84a3b9 100644 --- a/Tests/RunCMake/Instrumentation/hook.cmake +++ b/Tests/RunCMake/Instrumentation/hook.cmake @@ -68,3 +68,7 @@ has_key(vendorString ${staticSystemInformation} ${hasStaticInfo}) if (NOT ERROR_MESSAGE MATCHES "^$") message(FATAL_ERROR ${ERROR_MESSAGE}) endif() + +get_filename_component(dataDir ${index} DIRECTORY) +get_filename_component(v1 ${dataDir} DIRECTORY) +file(TOUCH ${v1}/${hook}.hook) diff --git a/Tests/RunCMake/Instrumentation/query/cmake-command-ninja.cmake b/Tests/RunCMake/Instrumentation/query/cmake-command-ninja.cmake new file mode 100644 index 0000000..60acebd --- /dev/null +++ b/Tests/RunCMake/Instrumentation/query/cmake-command-ninja.cmake @@ -0,0 +1,6 @@ +cmake_instrumentation( + API_VERSION 1 + DATA_VERSION 1 + HOOKS preBuild postBuild + CALLBACK "\"${CMAKE_COMMAND}\" -P \"${CMAKE_SOURCE_DIR}/../hook.cmake\" 0" +) -- cgit v0.12 From 2680f30cafd8aeda16e4e3ad94def2d3b5de1d4e Mon Sep 17 00:00:00 2001 From: Martin Duffy Date: Mon, 3 Feb 2025 08:41:42 -0500 Subject: instrumentation: Allow multiple CALLBACK arguments Don't require quotes around CALLBACK argument and allow it to be passed multiple times. --- Help/command/cmake_instrumentation.rst | 8 +++++--- Source/cmInstrumentation.cxx | 11 ++++++----- Source/cmInstrumentation.h | 6 +++--- Source/cmInstrumentationCommand.cxx | 12 ++++-------- Tests/RunCMake/Instrumentation/query/cmake-command.cmake | 5 +++-- .../RunCMake/Instrumentation/query/generated/query-2.json.in | 3 ++- 6 files changed, 23 insertions(+), 22 deletions(-) diff --git a/Help/command/cmake_instrumentation.rst b/Help/command/cmake_instrumentation.rst index 4eb0c22..75427d4 100644 --- a/Help/command/cmake_instrumentation.rst +++ b/Help/command/cmake_instrumentation.rst @@ -23,8 +23,8 @@ only supported value for both fields is 1. See :ref:`cmake-instrumentation v1` for details of the data output content and location. Each of the optional keywords ``HOOKS``, ``QUERIES``, and ``CALLBACK`` -correspond to one of the parameters to the :ref:`cmake-instrumentation v1 Query Files`. Note that the -``CALLBACK`` keyword only accepts a single callback. +correspond to one of the parameters to the :ref:`cmake-instrumentation v1 Query Files`. +The ``CALLBACK`` keyword can be provided multiple times to create multiple callbacks. Whenever ``cmake_instrumentation`` is invoked, a query file is generated in ``/.cmake/timing/v1/query/generated`` to enable instrumentation @@ -43,7 +43,8 @@ equivalent JSON query file. DATA_VERSION 1 HOOKS postGenerate preCMakeBuild postCMakeBuild QUERIES staticSystemInformation dynamicSystemInformation - CALLBACK "${CMAKE_COMMAND} -P /path/to/handle_data.cmake" + CALLBACK ${CMAKE_COMMAND} -P /path/to/handle_data.cmake + CALLBACK ${CMAKE_COMMAND} -P /path/to/handle_data_2.cmake ) .. code-block:: json @@ -58,5 +59,6 @@ equivalent JSON query file. ], "callbacks": [ "/path/to/cmake -P /path/to/handle_data.cmake" + "/path/to/cmake -P /path/to/handle_data_2.cmake" ] } diff --git a/Source/cmInstrumentation.cxx b/Source/cmInstrumentation.cxx index 245195f..5877323 100644 --- a/Source/cmInstrumentation.cxx +++ b/Source/cmInstrumentation.cxx @@ -80,8 +80,9 @@ void cmInstrumentation::ReadJSONQuery(std::string const& file) } void cmInstrumentation::WriteJSONQuery( - std::set& queries_, - std::set& hooks_, std::string& callback) + std::set const& queries_, + std::set const& hooks_, + std::vector> const& callbacks_) { Json::Value root; root["version"] = 1; @@ -94,8 +95,8 @@ void cmInstrumentation::WriteJSONQuery( root["hooks"].append(cmInstrumentationQuery::HookString[hook]); } root["callbacks"] = Json::arrayValue; - if (!callback.empty()) { - root["callbacks"].append(callback); + for (auto const& callback : callbacks_) { + root["callbacks"].append(cmInstrumentation::GetCommandStr(callback)); } cmsys::Directory d; int n = 0; @@ -455,7 +456,7 @@ std::string cmInstrumentation::GetCommandStr( for (size_t i = 0; i < args.size(); ++i) { command_str = cmStrCat(command_str, args[i]); if (i < args.size() - 1) { - command_str = cmStrCat(command_str, " "); + command_str = cmStrCat(command_str, ' '); } } return command_str; diff --git a/Source/cmInstrumentation.h b/Source/cmInstrumentation.h index e9eceac..d17e99c 100644 --- a/Source/cmInstrumentation.h +++ b/Source/cmInstrumentation.h @@ -41,9 +41,9 @@ public: bool HasPreOrPostBuildHook() const; bool ReadJSONQueries(std::string const& directory); void ReadJSONQuery(std::string const& file); - void WriteJSONQuery(std::set& queries, - std::set& hooks, - std::string& callback); + void WriteJSONQuery(std::set const& queries, + std::set const& hooks, + std::vector> const& callback); void ClearGeneratedQueries(); int CollectTimingData(cmInstrumentationQuery::Hook hook); int SpawnBuildDaemon(); diff --git a/Source/cmInstrumentationCommand.cxx b/Source/cmInstrumentationCommand.cxx index 406ced8..3b530ee 100644 --- a/Source/cmInstrumentationCommand.cxx +++ b/Source/cmInstrumentationCommand.cxx @@ -81,7 +81,7 @@ bool cmInstrumentationCommand(std::vector const& args, ArgumentParser::NonEmpty DataVersion; ArgumentParser::NonEmpty> Queries; ArgumentParser::NonEmpty> Hooks; - ArgumentParser::NonEmpty> Callback; + ArgumentParser::NonEmpty>> Callbacks; }; static auto const parser = cmArgumentParser{} @@ -89,7 +89,7 @@ bool cmInstrumentationCommand(std::vector const& args, .Bind("DATA_VERSION"_s, &Arguments::DataVersion) .Bind("QUERIES"_s, &Arguments::Queries) .Bind("HOOKS"_s, &Arguments::Hooks) - .Bind("CALLBACK"_s, &Arguments::Callback); + .Bind("CALLBACK"_s, &Arguments::Callbacks); std::vector unparsedArguments; Arguments const arguments = parser.Parse(args, &unparsedArguments); @@ -137,14 +137,10 @@ bool cmInstrumentationCommand(std::vector const& args, hooks.insert(hook); } - std::string callback; - for (auto const& arg : arguments.Callback) { - callback = cmStrCat(callback, arg); - } - status.GetMakefile() .GetCMakeInstance() ->GetInstrumentation() - ->WriteJSONQuery(queries, hooks, callback); + ->WriteJSONQuery(queries, hooks, arguments.Callbacks); + return true; } diff --git a/Tests/RunCMake/Instrumentation/query/cmake-command.cmake b/Tests/RunCMake/Instrumentation/query/cmake-command.cmake index dbbebb1..3f66de5 100644 --- a/Tests/RunCMake/Instrumentation/query/cmake-command.cmake +++ b/Tests/RunCMake/Instrumentation/query/cmake-command.cmake @@ -8,7 +8,7 @@ API_VERSION 1 DATA_VERSION 1 HOOKS postGenerate - CALLBACK "\"${CMAKE_COMMAND}\" -E echo callback1" + CALLBACK \"${CMAKE_COMMAND}\" -E echo callback1 ) # Query 2 cmake_instrumentation( @@ -16,5 +16,6 @@ DATA_VERSION 1 HOOKS postCMakeBuild QUERIES staticSystemInformation dynamicSystemInformation - CALLBACK "\"${CMAKE_COMMAND}\" -E echo callback2" + CALLBACK \"${CMAKE_COMMAND}\" -E echo callback2 + CALLBACK \"${CMAKE_COMMAND}\" -E echo callback3 ) diff --git a/Tests/RunCMake/Instrumentation/query/generated/query-2.json.in b/Tests/RunCMake/Instrumentation/query/generated/query-2.json.in index 0e2bd31..3591633 100644 --- a/Tests/RunCMake/Instrumentation/query/generated/query-2.json.in +++ b/Tests/RunCMake/Instrumentation/query/generated/query-2.json.in @@ -1,7 +1,8 @@ { "callbacks" : [ - "\"@CMAKE_COMMAND@\" -E echo callback2" + "\"@CMAKE_COMMAND@\" -E echo callback2", + "\"@CMAKE_COMMAND@\" -E echo callback3" ], "hooks" : [ -- cgit v0.12