| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmCTestHandlerCommand.h" | |
| #include <algorithm> | |
| #include <cstdlib> | |
| #include <cm/string_view> | |
| #include "cmCMakePresetsGraph.h" | |
| #include "cmCTest.h" | |
| #include "cmCTestGenericHandler.h" | |
| #include "cmExecutionStatus.h" | |
| #include "cmJSONState.h" | |
| #include "cmMakefile.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| #include "cmValue.h" | |
| #include "cmWorkingDirectory.h" | |
| namespace { | |
| // class to save and restore the error state for ctest_* commands | |
| // if a ctest_* command has a CAPTURE_CMAKE_ERROR then put the error | |
| // state into there and restore the system wide error to what | |
| // it was before the command ran | |
| class SaveRestoreErrorState | |
| { | |
| public: | |
| SaveRestoreErrorState() | |
| { | |
| this->InitialErrorState = cmSystemTools::GetErrorOccurredFlag(); | |
| cmSystemTools::ResetErrorOccurredFlag(); // rest the error state | |
| this->CaptureCMakeErrorValue = false; | |
| } | |
| // if the function has a CAPTURE_CMAKE_ERROR then we should restore | |
| // the error state to what it was before the function was run | |
| // if not then let the error state be what it is | |
| void CaptureCMakeError() { this->CaptureCMakeErrorValue = true; } | |
| ~SaveRestoreErrorState() | |
| { | |
| // if we are not saving the return value then make sure | |
| // if it was in error it goes back to being in error | |
| // otherwise leave it be what it is | |
| if (!this->CaptureCMakeErrorValue) { | |
| if (this->InitialErrorState) { | |
| cmSystemTools::SetErrorOccurred(); | |
| } | |
| return; | |
| } | |
| // if we have saved the error in a return variable | |
| // then put things back exactly like they were | |
| bool currentState = cmSystemTools::GetErrorOccurredFlag(); | |
| // if the state changed during this command we need | |
| // to handle it, if not then nothing needs to be done | |
| if (currentState != this->InitialErrorState) { | |
| // restore the initial error state | |
| if (this->InitialErrorState) { | |
| cmSystemTools::SetErrorOccurred(); | |
| } else { | |
| cmSystemTools::ResetErrorOccurredFlag(); | |
| } | |
| } | |
| } | |
| SaveRestoreErrorState(SaveRestoreErrorState const&) = delete; | |
| SaveRestoreErrorState& operator=(SaveRestoreErrorState const&) = delete; | |
| private: | |
| bool InitialErrorState; | |
| bool CaptureCMakeErrorValue; | |
| }; | |
| } | |
| bool cmCTestHandlerCommand::InvokeImpl( | |
| BasicArguments& args, std::vector<std::string> const& unparsed, | |
| cmExecutionStatus& status, std::function<bool()> handler) const | |
| { | |
| // save error state and restore it if needed | |
| SaveRestoreErrorState errorState; | |
| if (!args.CaptureCMakeError.empty()) { | |
| errorState.CaptureCMakeError(); | |
| } | |
| bool success = [&]() -> bool { | |
| if (args.MaybeReportError(status.GetMakefile())) { | |
| return true; | |
| } | |
| std::sort(args.ParsedKeywords.begin(), args.ParsedKeywords.end()); | |
| auto const it = std::adjacent_find(args.ParsedKeywords.begin(), | |
| args.ParsedKeywords.end()); | |
| if (it != args.ParsedKeywords.end()) { | |
| status.SetError(cmStrCat("called with more than one value for ", *it)); | |
| return false; | |
| } | |
| if (!unparsed.empty()) { | |
| status.SetError( | |
| cmStrCat("called with unknown argument \"", unparsed.front(), "\".")); | |
| return false; | |
| } | |
| return handler(); | |
| }(); | |
| if (args.CaptureCMakeError.empty()) { | |
| return success; | |
| } | |
| if (!success) { | |
| cmCTestLog(this->CTest, ERROR_MESSAGE, this->GetName(), ' ', | |
| status.GetError(), '\n'); | |
| } | |
| cmMakefile& mf = status.GetMakefile(); | |
| success = success && !cmSystemTools::GetErrorOccurredFlag(); | |
| mf.AddDefinition(args.CaptureCMakeError, success ? "0" : "-1"); | |
| return true; | |
| } | |
| bool cmCTestHandlerCommand::ExecuteHandlerCommand( | |
| HandlerArguments& args, cmExecutionStatus& status) const | |
| { | |
| cmMakefile& mf = status.GetMakefile(); | |
| // Process input arguments. | |
| this->CheckArguments(args, status); | |
| // Set the config type of this ctest to the current value of the | |
| // CTEST_CONFIGURATION_TYPE script variable if it is defined. | |
| // The current script value trumps the -C argument on the command | |
| // line. | |
| cmValue ctestConfigType = mf.GetDefinition("CTEST_CONFIGURATION_TYPE"); | |
| if (ctestConfigType) { | |
| this->CTest->SetConfigType(*ctestConfigType); | |
| } | |
| if (!args.Build.empty()) { | |
| this->CTest->SetCTestConfiguration( | |
| "BuildDirectory", cmSystemTools::CollapseFullPath(args.Build), | |
| args.Quiet); | |
| } else { | |
| std::string const& bdir = mf.GetSafeDefinition("CTEST_BINARY_DIRECTORY"); | |
| if (!bdir.empty()) { | |
| this->CTest->SetCTestConfiguration( | |
| "BuildDirectory", cmSystemTools::CollapseFullPath(bdir), args.Quiet); | |
| } else { | |
| cmCTestLog(this->CTest, ERROR_MESSAGE, | |
| "CTEST_BINARY_DIRECTORY not set\n"); | |
| } | |
| } | |
| if (!args.Source.empty()) { | |
| cmCTestLog(this->CTest, DEBUG, "Set source directory to: ", args.Source, | |
| '\n'); | |
| this->CTest->SetCTestConfiguration( | |
| "SourceDirectory", cmSystemTools::CollapseFullPath(args.Source), | |
| args.Quiet); | |
| } else { | |
| this->CTest->SetCTestConfiguration( | |
| "SourceDirectory", | |
| cmSystemTools::CollapseFullPath( | |
| mf.GetSafeDefinition("CTEST_SOURCE_DIRECTORY")), | |
| args.Quiet); | |
| } | |
| if (cmValue changeId = mf.GetDefinition("CTEST_CHANGE_ID")) { | |
| this->CTest->SetCTestConfiguration("ChangeId", *changeId, args.Quiet); | |
| } | |
| cmCTestLog(this->CTest, DEBUG, "Initialize handler\n"); | |
| auto handler = this->InitializeHandler(args, status); | |
| if (!handler) { | |
| cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot instantiate test handler ", | |
| this->GetName(), '\n'); | |
| return false; | |
| } | |
| handler->SetAppendXML(args.Append); | |
| handler->PopulateCustomVectors(&mf); | |
| if (!args.SubmitIndex.empty()) { | |
| handler->SetSubmitIndex(atoi(args.SubmitIndex.c_str())); | |
| } | |
| cmWorkingDirectory workdir( | |
| this->CTest->GetCTestConfiguration("BuildDirectory")); | |
| if (workdir.Failed()) { | |
| status.SetError(workdir.GetError()); | |
| return false; | |
| } | |
| // reread time limit, as the variable may have been modified. | |
| this->CTest->SetTimeLimit(mf.GetDefinition("CTEST_TIME_LIMIT")); | |
| handler->SetCMakeInstance(mf.GetCMakeInstance()); | |
| int res = handler->ProcessHandler(); | |
| if (!args.ReturnValue.empty()) { | |
| mf.AddDefinition(args.ReturnValue, std::to_string(res)); | |
| } | |
| this->ProcessAdditionalValues(handler.get(), args, status); | |
| return true; | |
| } | |
| void cmCTestHandlerCommand::CheckArguments(HandlerArguments&, | |
| cmExecutionStatus&) const | |
| { | |
| } | |
| std::unique_ptr<cmCTestGenericHandler> | |
| cmCTestHandlerCommand::InitializeHandler(HandlerArguments&, | |
| cmExecutionStatus&) const | |
| { | |
| return nullptr; | |
| }; | |
| void cmCTestHandlerCommand::ProcessAdditionalValues(cmCTestGenericHandler*, | |
| HandlerArguments const&, | |
| cmExecutionStatus&) const | |
| { | |
| } | |
| namespace { | |
| enum class PresetType | |
| { | |
| Build, | |
| Test, | |
| }; | |
| cmCTestHandlerCommand::PresetCheckResult PresetExistsInGraph( | |
| cmCMakePresetsGraph& graph, std::string const& name, | |
| std::string const& sourceDir, std::string const& presetsFile, | |
| PresetType presetType, std::string& errorMessage) | |
| { | |
| using Result = cmCTestHandlerCommand::PresetCheckResult; | |
| if (!graph.ReadProjectPresets(sourceDir, presetsFile)) { | |
| errorMessage = cmStrCat("Could not read presets from \"", sourceDir, | |
| "\":\n ", graph.parseState.GetErrorMessage()); | |
| return Result::ReadError; | |
| } | |
| using Status = cmCMakePresetsGraph::PresetResolveStatus; | |
| if (presetType == PresetType::Build) { | |
| auto result = graph.ResolvePreset(name, graph.BuildPresets); | |
| return result.StatusCode == Status::Success ? Result::Found | |
| : Result::NotFound; | |
| } | |
| auto result = graph.ResolvePreset(name, graph.TestPresets); | |
| return result.StatusCode == Status::Success ? Result::Found | |
| : Result::NotFound; | |
| } | |
| } | |
| cmCTestHandlerCommand::PresetCheckResult | |
| cmCTestHandlerCommand::BuildPresetExists(std::string const& name, | |
| std::string const& sourceDir, | |
| std::string const& presetsFile, | |
| std::string& errorMessage) | |
| { | |
| cmCMakePresetsGraph graph; | |
| return PresetExistsInGraph(graph, name, sourceDir, presetsFile, | |
| PresetType::Build, errorMessage); | |
| } | |
| cmCTestHandlerCommand::PresetCheckResult | |
| cmCTestHandlerCommand::TestPresetExists(std::string const& name, | |
| std::string const& sourceDir, | |
| std::string const& presetsFile, | |
| std::string& errorMessage) | |
| { | |
| cmCMakePresetsGraph graph; | |
| return PresetExistsInGraph(graph, name, sourceDir, presetsFile, | |
| PresetType::Test, errorMessage); | |
| } |