| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 | /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying   file Copyright.txt or https://cmake.org/licensing for details.  */#include "cmCTestBuildAndTestHandler.h"#include "cmCTest.h"#include "cmCTestTestHandler.h"#include "cmGlobalGenerator.h"#include "cmMakefile.h"#include "cmSystemTools.h"#include "cmWorkingDirectory.h"#include "cmake.h"#include "cmsys/Process.h"#include <chrono>#include <cstring>#include <ratio>#include <stdlib.h>cmCTestBuildAndTestHandler::cmCTestBuildAndTestHandler(){  this->BuildTwoConfig = false;  this->BuildNoClean = false;  this->BuildNoCMake = false;  this->Timeout = cmDuration::zero();}void cmCTestBuildAndTestHandler::Initialize(){  this->BuildTargets.clear();  this->Superclass::Initialize();}const char* cmCTestBuildAndTestHandler::GetOutput(){  return this->Output.c_str();}int cmCTestBuildAndTestHandler::ProcessHandler(){  this->Output.clear();  std::string output;  cmSystemTools::ResetErrorOccuredFlag();  int retv = this->RunCMakeAndTest(&this->Output);  cmSystemTools::ResetErrorOccuredFlag();  return retv;}int cmCTestBuildAndTestHandler::RunCMake(std::string* outstring,                                         std::ostringstream& out,                                         std::string& cmakeOutString,                                         cmake* cm){  std::vector<std::string> args;  args.push_back(cmSystemTools::GetCMakeCommand());  args.push_back(this->SourceDir);  if (!this->BuildGenerator.empty()) {    args.push_back("-G" + this->BuildGenerator);  }  if (!this->BuildGeneratorPlatform.empty()) {    args.push_back("-A" + this->BuildGeneratorPlatform);  }  if (!this->BuildGeneratorToolset.empty()) {    args.push_back("-T" + this->BuildGeneratorToolset);  }  const char* config = nullptr;  if (!this->CTest->GetConfigType().empty()) {    config = this->CTest->GetConfigType().c_str();  }#ifdef CMAKE_INTDIR  if (!config) {    config = CMAKE_INTDIR;  }#endif  if (config) {    args.push_back("-DCMAKE_BUILD_TYPE:STRING=" + std::string(config));  }  for (std::string const& opt : this->BuildOptions) {    args.push_back(opt);  }  if (cm->Run(args) != 0) {    out << "Error: cmake execution failed\n";    out << cmakeOutString << "\n";    if (outstring) {      *outstring = out.str();    } else {      cmCTestLog(this->CTest, ERROR_MESSAGE, out.str() << std::endl);    }    return 1;  }  // do another config?  if (this->BuildTwoConfig) {    if (cm->Run(args) != 0) {      out << "Error: cmake execution failed\n";      out << cmakeOutString << "\n";      if (outstring) {        *outstring = out.str();      } else {        cmCTestLog(this->CTest, ERROR_MESSAGE, out.str() << std::endl);      }      return 1;    }  }  out << "======== CMake output     ======\n";  out << cmakeOutString;  out << "======== End CMake output ======\n";  return 0;}void CMakeMessageCallback(const char* m, const char* /*unused*/,                          bool& /*unused*/, void* s){  std::string* out = static_cast<std::string*>(s);  *out += m;  *out += "\n";}void CMakeProgressCallback(const char* msg, float /*unused*/, void* s){  std::string* out = static_cast<std::string*>(s);  *out += msg;  *out += "\n";}void CMakeOutputCallback(const char* m, size_t len, void* s){  std::string* out = static_cast<std::string*>(s);  out->append(m, len);}class cmCTestBuildAndTestCaptureRAII{  cmake& CM;public:  cmCTestBuildAndTestCaptureRAII(cmake& cm, std::string& s)    : CM(cm)  {    cmSystemTools::SetMessageCallback(CMakeMessageCallback, &s);    cmSystemTools::SetStdoutCallback(CMakeOutputCallback, &s);    cmSystemTools::SetStderrCallback(CMakeOutputCallback, &s);    this->CM.SetProgressCallback(CMakeProgressCallback, &s);  }  ~cmCTestBuildAndTestCaptureRAII()  {    this->CM.SetProgressCallback(nullptr, nullptr);    cmSystemTools::SetStderrCallback(nullptr, nullptr);    cmSystemTools::SetStdoutCallback(nullptr, nullptr);    cmSystemTools::SetMessageCallback(nullptr, nullptr);  }};int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring){  // if the generator and make program are not specified then it is an error  if (this->BuildGenerator.empty()) {    if (outstring) {      *outstring = "--build-and-test requires that the generator "                   "be provided using the --build-generator "                   "command line option. ";    }    return 1;  }  cmake cm(cmake::RoleProject);  cm.SetHomeDirectory("");  cm.SetHomeOutputDirectory("");  std::string cmakeOutString;  cmCTestBuildAndTestCaptureRAII captureRAII(cm, cmakeOutString);  static_cast<void>(captureRAII);  std::ostringstream out;  if (this->CTest->GetConfigType().empty() && !this->ConfigSample.empty()) {    // use the config sample to set the ConfigType    std::string fullPath;    std::string resultingConfig;    std::vector<std::string> extraPaths;    std::vector<std::string> failed;    fullPath = cmCTestTestHandler::FindExecutable(      this->CTest, this->ConfigSample.c_str(), resultingConfig, extraPaths,      failed);    if (!fullPath.empty() && !resultingConfig.empty()) {      this->CTest->SetConfigType(resultingConfig.c_str());    }    out << "Using config sample with results: " << fullPath << " and "        << resultingConfig << std::endl;  }  // we need to honor the timeout specified, the timeout include cmake, build  // and test time  auto clock_start = std::chrono::steady_clock::now();  // make sure the binary dir is there  out << "Internal cmake changing into directory: " << this->BinaryDir      << std::endl;  if (!cmSystemTools::FileIsDirectory(this->BinaryDir)) {    cmSystemTools::MakeDirectory(this->BinaryDir);  }  cmWorkingDirectory workdir(this->BinaryDir);  if (workdir.Failed()) {    auto msg = "Failed to change working directory to " + this->BinaryDir +      " : " + std::strerror(workdir.GetLastResult()) + "\n";    if (outstring) {      *outstring = msg;    } else {      cmCTestLog(this->CTest, ERROR_MESSAGE, msg);    }    return 1;  }  if (this->BuildNoCMake) {    // Make the generator available for the Build call below.    cmGlobalGenerator* gen = cm.CreateGlobalGenerator(this->BuildGenerator);    cm.SetGlobalGenerator(gen);    if (!this->BuildGeneratorPlatform.empty()) {      cmMakefile mf(gen, cm.GetCurrentSnapshot());      if (!gen->SetGeneratorPlatform(this->BuildGeneratorPlatform, &mf)) {        return 1;      }    }    // Load the cache to make CMAKE_MAKE_PROGRAM available.    cm.LoadCache(this->BinaryDir);  } else {    // do the cmake step, no timeout here since it is not a sub process    if (this->RunCMake(outstring, out, cmakeOutString, &cm)) {      return 1;    }  }  // do the build  if (this->BuildTargets.empty()) {    this->BuildTargets.push_back("");  }  for (std::string const& tar : this->BuildTargets) {    cmDuration remainingTime = std::chrono::seconds(0);    if (this->Timeout > cmDuration::zero()) {      remainingTime =        this->Timeout - (std::chrono::steady_clock::now() - clock_start);      if (remainingTime <= std::chrono::seconds(0)) {        if (outstring) {          *outstring = "--build-and-test timeout exceeded. ";        }        return 1;      }    }    std::string output;    const char* config = nullptr;    if (!this->CTest->GetConfigType().empty()) {      config = this->CTest->GetConfigType().c_str();    }#ifdef CMAKE_INTDIR    if (!config) {      config = CMAKE_INTDIR;    }#endif    if (!config) {      config = "Debug";    }    int retVal = cm.GetGlobalGenerator()->Build(      cmake::NO_BUILD_PARALLEL_LEVEL, this->SourceDir, this->BinaryDir,      this->BuildProject, tar, output, this->BuildMakeProgram, config,      !this->BuildNoClean, false, false, remainingTime);    out << output;    // if the build failed then return    if (retVal) {      if (outstring) {        *outstring = out.str();      }      return 1;    }  }  if (outstring) {    *outstring = out.str();  }  // if no test was specified then we are done  if (this->TestCommand.empty()) {    return 0;  }  // now run the compiled test if we can find it  // store the final location in fullPath  std::string fullPath;  std::string resultingConfig;  std::vector<std::string> extraPaths;  // if this->ExecutableDirectory is set try that as well  if (!this->ExecutableDirectory.empty()) {    std::string tempPath = this->ExecutableDirectory;    tempPath += "/";    tempPath += this->TestCommand;    extraPaths.push_back(tempPath);  }  std::vector<std::string> failed;  fullPath =    cmCTestTestHandler::FindExecutable(this->CTest, this->TestCommand.c_str(),                                       resultingConfig, extraPaths, failed);  if (!cmSystemTools::FileExists(fullPath)) {    out << "Could not find path to executable, perhaps it was not built: "        << this->TestCommand << "\n";    out << "tried to find it in these places:\n";    out << fullPath << "\n";    for (std::string const& fail : failed) {      out << fail << "\n";    }    if (outstring) {      *outstring = out.str();    } else {      cmCTestLog(this->CTest, ERROR_MESSAGE, out.str());    }    return 1;  }  std::vector<const char*> testCommand;  testCommand.push_back(fullPath.c_str());  for (std::string const& testCommandArg : this->TestCommandArgs) {    testCommand.push_back(testCommandArg.c_str());  }  testCommand.push_back(nullptr);  std::string outs;  int retval = 0;  // run the test from the this->BuildRunDir if set  if (!this->BuildRunDir.empty()) {    out << "Run test in directory: " << this->BuildRunDir << "\n";    if (!workdir.SetDirectory(this->BuildRunDir)) {      out << "Failed to change working directory : "          << std::strerror(workdir.GetLastResult()) << "\n";      if (outstring) {        *outstring = out.str();      } else {        cmCTestLog(this->CTest, ERROR_MESSAGE, out.str());      }      return 1;    }  }  out << "Running test command: \"" << fullPath << "\"";  for (std::string const& testCommandArg : this->TestCommandArgs) {    out << " \"" << testCommandArg << "\"";  }  out << "\n";  // how much time is remaining  cmDuration remainingTime = std::chrono::seconds(0);  if (this->Timeout > cmDuration::zero()) {    remainingTime =      this->Timeout - (std::chrono::steady_clock::now() - clock_start);    if (remainingTime <= std::chrono::seconds(0)) {      if (outstring) {        *outstring = "--build-and-test timeout exceeded. ";      }      return 1;    }  }  int runTestRes = this->CTest->RunTest(testCommand, &outs, &retval, nullptr,                                        remainingTime, nullptr);  if (runTestRes != cmsysProcess_State_Exited || retval != 0) {    out << "Test command failed: " << testCommand[0] << "\n";    retval = 1;  }  out << outs << "\n";  if (outstring) {    *outstring = out.str();  } else {    cmCTestLog(this->CTest, OUTPUT, out.str() << std::endl);  }  return retval;}int cmCTestBuildAndTestHandler::ProcessCommandLineArguments(  const std::string& currentArg, size_t& idx,  const std::vector<std::string>& allArgs){  // --build-and-test options  if (currentArg.find("--build-and-test", 0) == 0 &&      idx < allArgs.size() - 1) {    if (idx + 2 < allArgs.size()) {      idx++;      this->SourceDir = allArgs[idx];      idx++;      this->BinaryDir = allArgs[idx];      // dir must exist before CollapseFullPath is called      cmSystemTools::MakeDirectory(this->BinaryDir);      this->BinaryDir = cmSystemTools::CollapseFullPath(this->BinaryDir);      this->SourceDir = cmSystemTools::CollapseFullPath(this->SourceDir);    } else {      cmCTestLog(this->CTest, ERROR_MESSAGE,                 "--build-and-test must have source and binary dir"                   << std::endl);      return 0;    }  }  if (currentArg.find("--build-target", 0) == 0 && idx < allArgs.size() - 1) {    idx++;    this->BuildTargets.push_back(allArgs[idx]);  }  if (currentArg.find("--build-nocmake", 0) == 0) {    this->BuildNoCMake = true;  }  if (currentArg.find("--build-run-dir", 0) == 0 && idx < allArgs.size() - 1) {    idx++;    this->BuildRunDir = allArgs[idx];  }  if (currentArg.find("--build-two-config", 0) == 0) {    this->BuildTwoConfig = true;  }  if (currentArg.find("--build-exe-dir", 0) == 0 && idx < allArgs.size() - 1) {    idx++;    this->ExecutableDirectory = allArgs[idx];  }  if (currentArg.find("--test-timeout", 0) == 0 && idx < allArgs.size() - 1) {    idx++;    this->Timeout = cmDuration(atof(allArgs[idx].c_str()));  }  if (currentArg == "--build-generator" && idx < allArgs.size() - 1) {    idx++;    this->BuildGenerator = allArgs[idx];  }  if (currentArg == "--build-generator-platform" && idx < allArgs.size() - 1) {    idx++;    this->BuildGeneratorPlatform = allArgs[idx];  }  if (currentArg == "--build-generator-toolset" && idx < allArgs.size() - 1) {    idx++;    this->BuildGeneratorToolset = allArgs[idx];  }  if (currentArg.find("--build-project", 0) == 0 && idx < allArgs.size() - 1) {    idx++;    this->BuildProject = allArgs[idx];  }  if (currentArg.find("--build-makeprogram", 0) == 0 &&      idx < allArgs.size() - 1) {    idx++;    this->BuildMakeProgram = allArgs[idx];  }  if (currentArg.find("--build-config-sample", 0) == 0 &&      idx < allArgs.size() - 1) {    idx++;    this->ConfigSample = allArgs[idx];  }  if (currentArg.find("--build-noclean", 0) == 0) {    this->BuildNoClean = true;  }  if (currentArg.find("--build-options", 0) == 0) {    while (idx + 1 < allArgs.size() && allArgs[idx + 1] != "--build-target" &&           allArgs[idx + 1] != "--test-command") {      ++idx;      this->BuildOptions.push_back(allArgs[idx]);    }  }  if (currentArg.find("--test-command", 0) == 0 && idx < allArgs.size() - 1) {    ++idx;    this->TestCommand = allArgs[idx];    while (idx + 1 < allArgs.size()) {      ++idx;      this->TestCommandArgs.push_back(allArgs[idx]);    }  }  return 1;}
 |