| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file LICENSE.rst or https://cmake.org/licensing for details. */
- #include "cmCTestBuildAndTest.h"
- #include <chrono>
- #include <cstdint>
- #include <iostream>
- #include <ratio>
- #include <utility>
- #include <cm3p/uv.h>
- #include "cmBuildOptions.h"
- #include "cmCTest.h"
- #include "cmCTestTestHandler.h"
- #include "cmGlobalGenerator.h"
- #include "cmMakefile.h"
- #include "cmProcessOutput.h"
- #include "cmState.h"
- #include "cmStringAlgorithms.h"
- #include "cmSystemTools.h"
- #include "cmUVHandlePtr.h"
- #include "cmUVProcessChain.h"
- #include "cmUVStream.h"
- #include "cmWorkingDirectory.h"
- #include "cmake.h"
- struct cmMessageMetadata;
- cmCTestBuildAndTest::cmCTestBuildAndTest(cmCTest* ctest)
- : CTest(ctest)
- {
- }
- bool cmCTestBuildAndTest::RunCMake(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);
- }
- char const* config = nullptr;
- if (!this->CTest->GetConfigType().empty()) {
- config = this->CTest->GetConfigType().c_str();
- }
- if (config) {
- args.push_back("-DCMAKE_BUILD_TYPE:STRING=" + std::string(config));
- }
- if (!this->BuildMakeProgram.empty() &&
- (this->BuildGenerator.find("Make") != std::string::npos ||
- this->BuildGenerator.find("Ninja") != std::string::npos ||
- this->BuildGenerator.find("FASTBuild") != std::string::npos)) {
- args.push_back("-DCMAKE_MAKE_PROGRAM:FILEPATH=" + this->BuildMakeProgram);
- }
- for (std::string const& opt : this->BuildOptions) {
- args.push_back(opt);
- }
- std::cout << "======== CMake output ======\n";
- if (cm->Run(args) != 0) {
- std::cout << "======== End CMake output ======\n"
- "Error: cmake execution failed\n";
- return false;
- }
- // do another config?
- if (this->BuildTwoConfig) {
- if (cm->Run(args) != 0) {
- std::cout << "======== End CMake output ======\n"
- "Error: cmake execution failed\n";
- return false;
- }
- }
- std::cout << "======== End CMake output ======\n";
- return true;
- }
- bool cmCTestBuildAndTest::RunTest(std::vector<std::string> const& argv,
- int* retVal, cmDuration timeout)
- {
- cmUVProcessChainBuilder builder;
- builder.AddCommand(argv).SetMergedBuiltinStreams();
- auto chain = builder.Start();
- cmProcessOutput processOutput(cmProcessOutput::Auto);
- cm::uv_pipe_ptr outputStream;
- outputStream.init(chain.GetLoop(), 0);
- uv_pipe_open(outputStream, chain.OutputStream());
- auto outputHandle = cmUVStreamRead(
- outputStream,
- [&processOutput](std::vector<char> data) {
- std::string decoded;
- processOutput.DecodeText(data.data(), data.size(), decoded);
- std::cout << decoded << std::flush;
- },
- []() {});
- bool complete = chain.Wait(static_cast<uint64_t>(timeout.count() * 1000.0));
- bool result = false;
- if (complete) {
- auto const& status = chain.GetStatus(0);
- auto exception = status.GetException();
- switch (exception.first) {
- case cmUVProcessChain::ExceptionCode::None:
- *retVal = static_cast<int>(status.ExitStatus);
- result = true;
- break;
- case cmUVProcessChain::ExceptionCode::Spawn: {
- std::cout << "\n*** ERROR executing: " << exception.second;
- } break;
- default: {
- *retVal = status.TermSignal;
- std::cout << "\n*** Exception executing: " << exception.second;
- } break;
- }
- }
- return result;
- }
- class cmCTestBuildAndTestCaptureRAII
- {
- cmake& CM;
- public:
- cmCTestBuildAndTestCaptureRAII(cmake& cm)
- : CM(cm)
- {
- cmSystemTools::SetMessageCallback(
- [](std::string const& msg, cmMessageMetadata const& /* unused */) {
- std::cout << msg << std::endl;
- });
- cmSystemTools::SetStdoutCallback(
- [](std::string const& m) { std::cout << m << std::flush; });
- cmSystemTools::SetStderrCallback(
- [](std::string const& m) { std::cout << m << std::flush; });
- this->CM.SetProgressCallback([](std::string const& msg, float prog) {
- if (prog < 0) {
- std::cout << msg << std::endl;
- }
- });
- }
- ~cmCTestBuildAndTestCaptureRAII()
- {
- this->CM.SetProgressCallback(nullptr);
- cmSystemTools::SetStderrCallback(nullptr);
- cmSystemTools::SetStdoutCallback(nullptr);
- cmSystemTools::SetMessageCallback(nullptr);
- }
- cmCTestBuildAndTestCaptureRAII(cmCTestBuildAndTestCaptureRAII const&) =
- delete;
- cmCTestBuildAndTestCaptureRAII& operator=(
- cmCTestBuildAndTestCaptureRAII const&) = delete;
- };
- int cmCTestBuildAndTest::Run()
- {
- // if the generator and make program are not specified then it is an error
- if (this->BuildGenerator.empty()) {
- std::cout << "--build-and-test requires that the generator "
- "be provided using the --build-generator "
- "command line option.\n";
- return 1;
- }
- cmake cm(cmake::RoleProject, cmState::Project);
- cmCTestBuildAndTestCaptureRAII captureRAII(cm);
- static_cast<void>(captureRAII);
- 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, resultingConfig, extraPaths, failed);
- if (!fullPath.empty() && !resultingConfig.empty()) {
- this->CTest->SetConfigType(resultingConfig);
- }
- std::cout << "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
- std::cout << "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()) {
- std::cout << workdir.GetError() << '\n';
- return 1;
- }
- if (this->BuildNoCMake) {
- // Make the generator available for the Build call below.
- cm.SetGlobalGenerator(cm.CreateGlobalGenerator(this->BuildGenerator));
- if (!this->BuildGeneratorPlatform.empty()) {
- cmMakefile mf(cm.GetGlobalGenerator(), cm.GetCurrentSnapshot());
- if (!cm.GetGlobalGenerator()->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(&cm)) {
- return 1;
- }
- }
- // do the build
- if (this->BuildTargets.empty()) {
- this->BuildTargets.emplace_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)) {
- std::cout << "--build-and-test timeout exceeded. ";
- return 1;
- }
- }
- char const* config = nullptr;
- if (!this->CTest->GetConfigType().empty()) {
- config = this->CTest->GetConfigType().c_str();
- }
- if (!config) {
- config = "Debug";
- }
- cmBuildOptions buildOptions(!this->BuildNoClean, false,
- PackageResolveMode::Disable);
- int retVal = cm.GetGlobalGenerator()->Build(
- cmake::NO_BUILD_PARALLEL_LEVEL, this->SourceDir, this->BinaryDir,
- this->BuildProject, { tar }, std::cout, this->BuildMakeProgram, config,
- buildOptions, false, remainingTime, cmSystemTools::OUTPUT_PASSTHROUGH);
- // if the build failed then return
- if (retVal) {
- return 1;
- }
- }
- // 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 =
- cmStrCat(this->ExecutableDirectory, '/', this->TestCommand);
- extraPaths.push_back(tempPath);
- }
- std::vector<std::string> failed;
- fullPath = cmCTestTestHandler::FindExecutable(
- this->CTest, this->TestCommand, resultingConfig, extraPaths, failed);
- if (!cmSystemTools::FileExists(fullPath)) {
- std::cout
- << "Could not find path to executable, perhaps it was not built: "
- << this->TestCommand << "\n"
- << "tried to find it in these places:\n"
- << fullPath << '\n';
- for (std::string const& fail : failed) {
- std::cout << fail << '\n';
- }
- return 1;
- }
- std::vector<std::string> testCommand;
- testCommand.push_back(fullPath);
- for (std::string const& testCommandArg : this->TestCommandArgs) {
- testCommand.push_back(testCommandArg);
- }
- int retval = 0;
- // run the test from the this->BuildRunDir if set
- if (!this->BuildRunDir.empty()) {
- std::cout << "Run test in directory: " << this->BuildRunDir << '\n';
- if (!workdir.SetDirectory(this->BuildRunDir)) {
- std::cout << workdir.GetError() << '\n';
- return 1;
- }
- }
- std::cout << "Running test command: \"" << fullPath << '"';
- for (std::string const& testCommandArg : this->TestCommandArgs) {
- std::cout << " \"" << testCommandArg << '"';
- }
- std::cout << '\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)) {
- std::cout << "--build-and-test timeout exceeded. ";
- return 1;
- }
- }
- bool runTestRes = this->RunTest(testCommand, &retval, remainingTime);
- if (!runTestRes || retval != 0) {
- std::cout << "\nTest command failed: " << testCommand[0] << '\n';
- retval = 1;
- }
- return retval;
- }
|