cmCTestScriptHandler.cxx 30.7 KB
Newer Older
1 2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
3 4 5
#include "cmCTestScriptHandler.h"

#include "cmCTest.h"
6
#include "cmCTestBuildCommand.h"
7
#include "cmCTestCommand.h"
8
#include "cmCTestConfigureCommand.h"
9
#include "cmCTestCoverageCommand.h"
10
#include "cmCTestEmptyBinaryDirectoryCommand.h"
11
#include "cmCTestMemCheckCommand.h"
12
#include "cmCTestReadCustomFilesCommand.h"
13 14
#include "cmCTestRunScriptCommand.h"
#include "cmCTestSleepCommand.h"
15
#include "cmCTestStartCommand.h"
16
#include "cmCTestSubmitCommand.h"
17 18
#include "cmCTestTestCommand.h"
#include "cmCTestUpdateCommand.h"
Zach's avatar
Zach committed
19
#include "cmCTestUploadCommand.h"
20 21 22 23
#include "cmFunctionBlocker.h"
#include "cmGeneratedFileStream.h"
#include "cmGlobalGenerator.h"
#include "cmMakefile.h"
24
#include "cmStateTypes.h"
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
#include "cmSystemTools.h"
#include "cmake.h"

#include <cmsys/Directory.hxx>
#include <cmsys/Process.h>
#include <map>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utility>

#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif

class cmExecutionStatus;
struct cmListFileFunction;
45

Andy Cedilnik's avatar
Andy Cedilnik committed
46 47
#define CTEST_INITIAL_CMAKE_OUTPUT_FILE_NAME "CTestInitialCMakeOutput.log"

48 49 50 51 52
// used to keep elapsed time up to date
class cmCTestScriptFunctionBlocker : public cmFunctionBlocker
{
public:
  cmCTestScriptFunctionBlocker() {}
53
  ~cmCTestScriptFunctionBlocker() CM_OVERRIDE {}
54
  bool IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile& mf,
55
                         cmExecutionStatus& /*status*/) CM_OVERRIDE;
56 57
  // virtual bool ShouldRemove(const cmListFileFunction& lff, cmMakefile &mf);
  // virtual void ScopeEnded(cmMakefile &mf);
Andy Cedilnik's avatar
Andy Cedilnik committed
58

Andy Cedilnik's avatar
Andy Cedilnik committed
59
  cmCTestScriptHandler* CTestScriptHandler;
60 61 62
};

// simply update the time and don't block anything
63 64 65
bool cmCTestScriptFunctionBlocker::IsFunctionBlocked(
  const cmListFileFunction& /*lff*/, cmMakefile& /*mf*/,
  cmExecutionStatus& /*status*/)
66
{
Andy Cedilnik's avatar
Andy Cedilnik committed
67
  this->CTestScriptHandler->UpdateElapsedTime();
68 69 70
  return false;
}

71 72
cmCTestScriptHandler::cmCTestScriptHandler()
{
Andy Cedilnik's avatar
Andy Cedilnik committed
73 74 75
  this->Backup = false;
  this->EmptyBinDir = false;
  this->EmptyBinDirOnce = false;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
76 77 78
  this->Makefile = CM_NULLPTR;
  this->CMake = CM_NULLPTR;
  this->GlobalGenerator = CM_NULLPTR;
79

Andy Cedilnik's avatar
Andy Cedilnik committed
80
  this->ScriptStartTime = 0;
Andy Cedilnik's avatar
Andy Cedilnik committed
81

82
  // the *60 is becuase the settings are in minutes but GetTime is seconds
83
  this->MinimumInterval = 30 * 60;
Andy Cedilnik's avatar
Andy Cedilnik committed
84
  this->ContinuousDuration = -1;
85 86
}

87 88
void cmCTestScriptHandler::Initialize()
{
89
  this->Superclass::Initialize();
Andy Cedilnik's avatar
Andy Cedilnik committed
90 91 92 93 94 95 96 97 98 99 100
  this->Backup = false;
  this->EmptyBinDir = false;
  this->EmptyBinDirOnce = false;

  this->SourceDir = "";
  this->BinaryDir = "";
  this->BackupSourceDir = "";
  this->BackupBinaryDir = "";
  this->CTestRoot = "";
  this->CVSCheckOut = "";
  this->CTestCmd = "";
101
  this->UpdateCmd = "";
Andy Cedilnik's avatar
Andy Cedilnik committed
102
  this->CTestEnv = "";
103
  this->InitialCache = "";
Andy Cedilnik's avatar
Andy Cedilnik committed
104 105 106 107
  this->CMakeCmd = "";
  this->CMOutFile = "";
  this->ExtraUpdates.clear();

108
  this->MinimumInterval = 20 * 60;
Andy Cedilnik's avatar
Andy Cedilnik committed
109
  this->ContinuousDuration = -1;
110 111

  // what time in seconds did this script start running
Andy Cedilnik's avatar
Andy Cedilnik committed
112
  this->ScriptStartTime = 0;
Andy Cedilnik's avatar
Andy Cedilnik committed
113

114
  delete this->Makefile;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
115
  this->Makefile = CM_NULLPTR;
116 117

  delete this->GlobalGenerator;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
118
  this->GlobalGenerator = CM_NULLPTR;
119 120

  delete this->CMake;
121
}
122 123 124

cmCTestScriptHandler::~cmCTestScriptHandler()
{
125
  delete this->Makefile;
126 127
  delete this->GlobalGenerator;
  delete this->CMake;
128 129 130
}

// just adds an argument to the vector
131
void cmCTestScriptHandler::AddConfigurationScript(const char* script,
Ken Martin's avatar
Ken Martin committed
132
                                                  bool pscope)
133
{
Andy Cedilnik's avatar
Andy Cedilnik committed
134
  this->ConfigurationScripts.push_back(script);
135
  this->ScriptProcessScope.push_back(pscope);
136 137 138 139
}

// the generic entry point for handling scripts, this routine will run all
// the scripts provides a -S arguments
140
int cmCTestScriptHandler::ProcessHandler()
141 142
{
  int res = 0;
143
  for (size_t i = 0; i < this->ConfigurationScripts.size(); ++i) {
144
    // for each script run it
145 146 147 148 149
    res |= this->RunConfigurationScript(
      cmSystemTools::CollapseFullPath(this->ConfigurationScripts[i]),
      this->ScriptProcessScope[i]);
  }
  if (res) {
150
    return -1;
151
  }
152
  return 0;
153 154
}

155 156
void cmCTestScriptHandler::UpdateElapsedTime()
{
157
  if (this->Makefile) {
158 159
    // set the current elapsed time
    char timeString[20];
160 161 162
    int itime = static_cast<unsigned int>(cmSystemTools::GetTime() -
                                          this->ScriptStartTime);
    sprintf(timeString, "%i", itime);
163
    this->Makefile->AddDefinition("CTEST_ELAPSED_TIME", timeString);
164
  }
165
}
166

167 168 169
void cmCTestScriptHandler::AddCTestCommand(cmCTestCommand* command)
{
  cmCTestCommand* newCom = command;
Andy Cedilnik's avatar
Andy Cedilnik committed
170 171
  newCom->CTest = this->CTest;
  newCom->CTestScriptHandler = this;
172
  this->CMake->GetState()->AddCommand(newCom);
173 174
}

175 176 177 178 179
int cmCTestScriptHandler::ExecuteScript(const std::string& total_script_arg)
{
  // execute the script passing in the arguments to the script as well as the
  // arguments from this invocation of cmake
  std::vector<const char*> argv;
180
  argv.push_back(cmSystemTools::GetCTestCommand().c_str());
181 182 183
  argv.push_back("-SR");
  argv.push_back(total_script_arg.c_str());

184 185
  cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Executable for CTest is: "
               << cmSystemTools::GetCTestCommand() << "\n");
186 187

  // now pass through all the other arguments
188
  std::vector<std::string>& initArgs =
189
    this->CTest->GetInitialCommandLineArguments();
Bill Hoffman's avatar
Bill Hoffman committed
190
  //*** need to make sure this does not have the current script ***
191
  for (size_t i = 1; i < initArgs.size(); ++i) {
192
    argv.push_back(initArgs[i].c_str());
193
  }
Daniel Pfeifer's avatar
Daniel Pfeifer committed
194
  argv.push_back(CM_NULLPTR);
195 196 197 198

  // Now create process object
  cmsysProcess* cp = cmsysProcess_New();
  cmsysProcess_SetCommand(cp, &*argv.begin());
199
  // cmsysProcess_SetWorkingDirectory(cp, dir);
200
  cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
201
  // cmsysProcess_SetTimeout(cp, timeout);
202 203 204 205 206 207
  cmsysProcess_Execute(cp);

  std::vector<char> out;
  std::vector<char> err;
  std::string line;
  int pipe = cmSystemTools::WaitForLine(cp, line, 100.0, out, err);
208 209 210 211
  while (pipe != cmsysProcess_Pipe_None) {
    cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Output: " << line
                                                               << "\n");
    if (pipe == cmsysProcess_Pipe_STDERR) {
212
      cmCTestLog(this->CTest, ERROR_MESSAGE, line << "\n");
213
    } else if (pipe == cmsysProcess_Pipe_STDOUT) {
214 215
      cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, line << "\n");
    }
216 217
    pipe = cmSystemTools::WaitForLine(cp, line, 100, out, err);
  }
Brad King's avatar
Brad King committed
218

219
  // Properly handle output of the build command
Daniel Pfeifer's avatar
Daniel Pfeifer committed
220
  cmsysProcess_WaitForExit(cp, CM_NULLPTR);
221 222
  int result = cmsysProcess_GetState(cp);
  int retVal = 0;
223
  bool failed = false;
224
  if (result == cmsysProcess_State_Exited) {
225
    retVal = cmsysProcess_GetExitValue(cp);
226
  } else if (result == cmsysProcess_State_Exception) {
227 228
    retVal = cmsysProcess_GetExitException(cp);
    cmCTestLog(this->CTest, ERROR_MESSAGE, "\tThere was an exception: "
229 230
                 << cmsysProcess_GetExceptionString(cp) << " " << retVal
                 << std::endl);
231
    failed = true;
232
  } else if (result == cmsysProcess_State_Expired) {
233
    cmCTestLog(this->CTest, ERROR_MESSAGE, "\tThere was a timeout"
234
                 << std::endl);
235
    failed = true;
236
  } else if (result == cmsysProcess_State_Error) {
237
    cmCTestLog(this->CTest, ERROR_MESSAGE, "\tError executing ctest: "
238
                 << cmsysProcess_GetErrorString(cp) << std::endl);
239
    failed = true;
240
  }
241
  cmsysProcess_Delete(cp);
242
  if (failed) {
243
    std::ostringstream message;
244 245
    message << "Error running command: [";
    message << result << "] ";
246 247 248 249
    for (std::vector<const char*>::iterator i = argv.begin(); i != argv.end();
         ++i) {
      if (*i) {
        message << *i << " ";
250
      }
251
    }
252 253 254 255
    cmCTestLog(this->CTest, ERROR_MESSAGE, message.str() << argv[0]
                                                         << std::endl);
    return -1;
  }
256 257 258
  return retVal;
}

259 260
static void ctestScriptProgressCallback(const char* m, float /*unused*/,
                                        void* cd)
261 262
{
  cmCTest* ctest = static_cast<cmCTest*>(cd);
263
  if (m && *m) {
264
    cmCTestLog(ctest, HANDLER_OUTPUT, "-- " << m << std::endl);
265
  }
266 267
}

268 269 270
void cmCTestScriptHandler::CreateCMake()
{
  // create a cmake instance to read the configuration script
271
  if (this->CMake) {
272 273
    delete this->CMake;
    delete this->GlobalGenerator;
274
    delete this->Makefile;
275
  }
276
  this->CMake = new cmake;
277 278
  this->CMake->SetHomeDirectory("");
  this->CMake->SetHomeOutputDirectory("");
279
  this->CMake->GetCurrentSnapshot().SetDefaultDefinitions();
280
  this->CMake->AddCMakePaths();
281
  this->GlobalGenerator = new cmGlobalGenerator(this->CMake);
282

283
  cmStateSnapshot snapshot = this->CMake->GetCurrentSnapshot();
284 285 286
  std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
  snapshot.GetDirectory().SetCurrentSource(cwd);
  snapshot.GetDirectory().SetCurrentBinary(cwd);
287
  this->Makefile = new cmMakefile(this->GlobalGenerator, snapshot);
288

289 290
  this->CMake->SetProgressCallback(ctestScriptProgressCallback, this->CTest);

Brad King's avatar
Brad King committed
291
  // remove all cmake commands which are not scriptable, since they can't be
292
  // used in ctest scripts
293
  this->CMake->GetState()->RemoveUnscriptableCommands();
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309

  // add any ctest specific commands, probably should have common superclass
  // for ctest commands to clean this up. If a couple more commands are
  // created with the same format lets do that - ken
  this->AddCTestCommand(new cmCTestBuildCommand);
  this->AddCTestCommand(new cmCTestConfigureCommand);
  this->AddCTestCommand(new cmCTestCoverageCommand);
  this->AddCTestCommand(new cmCTestEmptyBinaryDirectoryCommand);
  this->AddCTestCommand(new cmCTestMemCheckCommand);
  this->AddCTestCommand(new cmCTestReadCustomFilesCommand);
  this->AddCTestCommand(new cmCTestRunScriptCommand);
  this->AddCTestCommand(new cmCTestSleepCommand);
  this->AddCTestCommand(new cmCTestStartCommand);
  this->AddCTestCommand(new cmCTestSubmitCommand);
  this->AddCTestCommand(new cmCTestTestCommand);
  this->AddCTestCommand(new cmCTestUpdateCommand);
Zach's avatar
Zach committed
310
  this->AddCTestCommand(new cmCTestUploadCommand);
311 312
}

313
// this sets up some variables for the script to use, creates the required
314
// cmake instance and generators, and then reads in the script
315
int cmCTestScriptHandler::ReadInScript(const std::string& total_script_arg)
316
{
317 318 319
  // Reset the error flag so that the script is read in no matter what
  cmSystemTools::ResetErrorOccuredFlag();

320 321 322 323 324
  // if the argument has a , in it then it needs to be broken into the fist
  // argument (which is the script) and the second argument which will be
  // passed into the scripts as S_ARG
  std::string script = total_script_arg;
  std::string script_arg;
325 326 327 328
  const std::string::size_type comma_pos = total_script_arg.find(',');
  if (comma_pos != std::string::npos) {
    script = total_script_arg.substr(0, comma_pos);
    script_arg = total_script_arg.substr(comma_pos + 1);
329
  }
330
  // make sure the file exists
331
  if (!cmSystemTools::FileExists(script.c_str())) {
Andy Cedilnik's avatar
Andy Cedilnik committed
332
    cmSystemTools::Error("Cannot find file: ", script.c_str());
333
    return 1;
334
  }
335 336

  // read in the list file to fill the cache
337 338
  // create a cmake instance to read the configuration script
  this->CreateCMake();
Andy Cedilnik's avatar
Andy Cedilnik committed
339

340
  // set a variable with the path to the current script
341 342 343 344
  this->Makefile->AddDefinition(
    "CTEST_SCRIPT_DIRECTORY", cmSystemTools::GetFilenamePath(script).c_str());
  this->Makefile->AddDefinition(
    "CTEST_SCRIPT_NAME", cmSystemTools::GetFilenameName(script).c_str());
Andy Cedilnik's avatar
Andy Cedilnik committed
345
  this->Makefile->AddDefinition("CTEST_EXECUTABLE_NAME",
346
                                cmSystemTools::GetCTestCommand().c_str());
Andy Cedilnik's avatar
Andy Cedilnik committed
347
  this->Makefile->AddDefinition("CMAKE_EXECUTABLE_NAME",
348
                                cmSystemTools::GetCMakeCommand().c_str());
Andy Cedilnik's avatar
Andy Cedilnik committed
349
  this->Makefile->AddDefinition("CTEST_RUN_CURRENT_SCRIPT", true);
350
  this->UpdateElapsedTime();
Andy Cedilnik's avatar
Andy Cedilnik committed
351

352
  // add the script arg if defined
353
  if (!script_arg.empty()) {
Andy Cedilnik's avatar
Andy Cedilnik committed
354
    this->Makefile->AddDefinition("CTEST_SCRIPT_ARG", script_arg.c_str());
355
  }
356

357 358 359 360
#if defined(__CYGWIN__)
  this->Makefile->AddDefinition("CMAKE_LEGACY_CYGWIN_WIN32", "0");
#endif

361
  // always add a function blocker to update the elapsed time
362
  cmCTestScriptFunctionBlocker* f = new cmCTestScriptFunctionBlocker();
Andy Cedilnik's avatar
Andy Cedilnik committed
363 364
  f->CTestScriptHandler = this;
  this->Makefile->AddFunctionBlocker(f);
Andy Cedilnik's avatar
Andy Cedilnik committed
365

Brad King's avatar
Brad King committed
366 367
  /* Execute CTestScriptMode.cmake, which loads CMakeDetermineSystem and
  CMakeSystemSpecificInformation, so
368 369 370
  that variables like CMAKE_SYSTEM and also the search paths for libraries,
  header and executables are set correctly and can be used. Makes new-style
  ctest scripting easier. */
Brad King's avatar
Brad King committed
371
  std::string systemFile =
372
    this->Makefile->GetModulesFile("CTestScriptMode.cmake");
373
  if (!this->Makefile->ReadListFile(systemFile.c_str()) ||
374 375 376
      cmSystemTools::GetErrorOccuredFlag()) {
    cmCTestLog(this->CTest, ERROR_MESSAGE, "Error in read:" << systemFile
                                                            << "\n");
377
    return 2;
378
  }
379

380
  // Add definitions of variables passed in on the command line:
381
  const std::map<std::string, std::string>& defs =
382 383
    this->CTest->GetDefinitions();
  for (std::map<std::string, std::string>::const_iterator it = defs.begin();
384
       it != defs.end(); ++it) {
Stephen Kelly's avatar
Stephen Kelly committed
385
    this->Makefile->AddDefinition(it->first, it->second.c_str());
386
  }
387

388
  // finally read in the script
389
  if (!this->Makefile->ReadListFile(script.c_str()) ||
390
      cmSystemTools::GetErrorOccuredFlag()) {
Brad King's avatar
Brad King committed
391
    // Reset the error flag so that it can run more than
392
    // one script with an error when you use ctest_run_script.
393
    cmSystemTools::ResetErrorOccuredFlag();
394
    return 2;
395
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
396

397 398 399 400 401 402
  return 0;
}

// extract variabels from the script to set ivars
int cmCTestScriptHandler::ExtractVariables()
{
403 404 405
  // Temporary variables
  const char* minInterval;
  const char* contDuration;
406

407 408 409 410
  this->SourceDir =
    this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY");
  this->BinaryDir =
    this->Makefile->GetSafeDefinition("CTEST_BINARY_DIRECTORY");
411 412

  // add in translations for src and bin
413 414
  cmSystemTools::AddKeepPath(this->SourceDir);
  cmSystemTools::AddKeepPath(this->BinaryDir);
415

416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
  this->CTestCmd = this->Makefile->GetSafeDefinition("CTEST_COMMAND");
  this->CVSCheckOut = this->Makefile->GetSafeDefinition("CTEST_CVS_CHECKOUT");
  this->CTestRoot = this->Makefile->GetSafeDefinition("CTEST_DASHBOARD_ROOT");
  this->UpdateCmd = this->Makefile->GetSafeDefinition("CTEST_UPDATE_COMMAND");
  if (this->UpdateCmd.empty()) {
    this->UpdateCmd = this->Makefile->GetSafeDefinition("CTEST_CVS_COMMAND");
  }
  this->CTestEnv = this->Makefile->GetSafeDefinition("CTEST_ENVIRONMENT");
  this->InitialCache =
    this->Makefile->GetSafeDefinition("CTEST_INITIAL_CACHE");
  this->CMakeCmd = this->Makefile->GetSafeDefinition("CTEST_CMAKE_COMMAND");
  this->CMOutFile =
    this->Makefile->GetSafeDefinition("CTEST_CMAKE_OUTPUT_FILE_NAME");

  this->Backup = this->Makefile->IsOn("CTEST_BACKUP_AND_RESTORE");
  this->EmptyBinDir =
    this->Makefile->IsOn("CTEST_START_WITH_EMPTY_BINARY_DIRECTORY");
  this->EmptyBinDirOnce =
    this->Makefile->IsOn("CTEST_START_WITH_EMPTY_BINARY_DIRECTORY_ONCE");

  minInterval =
    this->Makefile->GetDefinition("CTEST_CONTINUOUS_MINIMUM_INTERVAL");
  contDuration = this->Makefile->GetDefinition("CTEST_CONTINUOUS_DURATION");
439 440 441

  char updateVar[40];
  int i;
442 443 444 445 446 447 448
  for (i = 1; i < 10; ++i) {
    sprintf(updateVar, "CTEST_EXTRA_UPDATES_%i", i);
    const char* updateVal = this->Makefile->GetDefinition(updateVar);
    if (updateVal) {
      if (this->UpdateCmd.empty()) {
        cmSystemTools::Error(
          updateVar, " specified without specifying CTEST_CVS_COMMAND.");
449 450
        return 12;
      }
451
      this->ExtraUpdates.push_back(updateVal);
452
    }
453
  }
454 455

  // in order to backup and restore we also must have the cvs root
456
  if (this->Backup && this->CVSCheckOut.empty()) {
457
    cmSystemTools::Error(
Andy Cedilnik's avatar
Andy Cedilnik committed
458
      "Backup was requested without specifying CTEST_CVS_CHECKOUT.");
459
    return 3;
460
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
461

462
  // make sure the required info is here
463 464
  if (this->SourceDir.empty() || this->BinaryDir.empty() ||
      this->CTestCmd.empty()) {
Andy Cedilnik's avatar
Andy Cedilnik committed
465 466 467 468 469 470
    std::string msg = "CTEST_SOURCE_DIRECTORY = ";
    msg += (!this->SourceDir.empty()) ? this->SourceDir.c_str() : "(Null)";
    msg += "\nCTEST_BINARY_DIRECTORY = ";
    msg += (!this->BinaryDir.empty()) ? this->BinaryDir.c_str() : "(Null)";
    msg += "\nCTEST_COMMAND = ";
    msg += (!this->CTestCmd.empty()) ? this->CTestCmd.c_str() : "(Null)";
471 472
    cmSystemTools::Error(
      "Some required settings in the configuration file were missing:\n",
Andy Cedilnik's avatar
Andy Cedilnik committed
473
      msg.c_str());
474
    return 4;
475
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
476

477
  // if the dashboard root isn't specified then we can compute it from the
Andy Cedilnik's avatar
Andy Cedilnik committed
478
  // this->SourceDir
479
  if (this->CTestRoot.empty()) {
480
    this->CTestRoot = cmSystemTools::GetFilenamePath(this->SourceDir);
481
  }
482 483

  // the script may override the minimum continuous interval
484
  if (minInterval) {
Andy Cedilnik's avatar
Andy Cedilnik committed
485
    this->MinimumInterval = 60 * atof(minInterval);
486 487
  }
  if (contDuration) {
Andy Cedilnik's avatar
Andy Cedilnik committed
488
    this->ContinuousDuration = 60.0 * atof(contDuration);
489
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
490

491 492
  this->UpdateElapsedTime();

493 494 495
  return 0;
}

496
void cmCTestScriptHandler::SleepInSeconds(unsigned int secondsToWait)
497 498
{
#if defined(_WIN32)
499
  Sleep(1000 * secondsToWait);
500
#else
501
  sleep(secondsToWait);
502 503 504 505
#endif
}

// run a specific script
506 507
int cmCTestScriptHandler::RunConfigurationScript(
  const std::string& total_script_arg, bool pscope)
508
{
509 510 511 512
#ifdef CMAKE_BUILD_WITH_CMAKE
  cmSystemTools::SaveRestoreEnvironment sre;
#endif

513
  int result;
Andy Cedilnik's avatar
Andy Cedilnik committed
514

515
  this->ScriptStartTime = cmSystemTools::GetTime();
Andy Cedilnik's avatar
Andy Cedilnik committed
516

517
  // read in the script
518
  if (pscope) {
519
    cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
520
               "Reading Script: " << total_script_arg << std::endl);
521
    result = this->ReadInScript(total_script_arg);
522
  } else {
523
    cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
524
               "Executing Script: " << total_script_arg << std::endl);
525
    result = this->ExecuteScript(total_script_arg);
526 527
  }
  if (result) {
528
    return result;
529
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
530

531
  // only run the curent script if we should
532
  if (this->Makefile && this->Makefile->IsOn("CTEST_RUN_CURRENT_SCRIPT")) {
533
    return this->RunCurrentScript();
534
  }
535 536 537
  return result;
}

538
int cmCTestScriptHandler::RunCurrentScript()
539 540 541
{
  int result;

542
  // do not run twice
Andy Cedilnik's avatar
Andy Cedilnik committed
543
  this->Makefile->AddDefinition("CTEST_RUN_CURRENT_SCRIPT", false);
544

545 546
  // no popup widows
  cmSystemTools::SetRunCommandHideConsole(true);
Andy Cedilnik's avatar
Andy Cedilnik committed
547

548 549
  // extract the vars from the cache and store in ivars
  result = this->ExtractVariables();
550
  if (result) {
551
    return result;
552
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
553

554
  // set any environment variables
555
  if (!this->CTestEnv.empty()) {
556
    std::vector<std::string> envArgs;
557
    cmSystemTools::ExpandListArgument(this->CTestEnv, envArgs);
558
    cmSystemTools::AppendEnv(envArgs);
559
  }
560 561 562 563

  // now that we have done most of the error checking finally run the
  // dashboard, we may be asked to repeatedly run this dashboard, such as
  // for a continuous, do we ned to run it more than once?
564
  if (this->ContinuousDuration >= 0) {
565
    this->UpdateElapsedTime();
566 567
    double ending_time = cmSystemTools::GetTime() + this->ContinuousDuration;
    if (this->EmptyBinDirOnce) {
Andy Cedilnik's avatar
Andy Cedilnik committed
568
      this->EmptyBinDir = true;
569 570
    }
    do {
571 572 573
      double interval = cmSystemTools::GetTime();
      result = this->RunConfigurationDashboard();
      interval = cmSystemTools::GetTime() - interval;
574
      if (interval < this->MinimumInterval) {
575
        this->SleepInSeconds(
Andy Cedilnik's avatar
Andy Cedilnik committed
576
          static_cast<unsigned int>(this->MinimumInterval - interval));
577 578
      }
      if (this->EmptyBinDirOnce) {
Andy Cedilnik's avatar
Andy Cedilnik committed
579
        this->EmptyBinDir = false;
580
      }
581 582
    } while (cmSystemTools::GetTime() < ending_time);
  }
583
  // otherwise just run it once
584
  else {
585
    result = this->RunConfigurationDashboard();
586
  }
587 588 589 590 591 592 593 594 595

  return result;
}

int cmCTestScriptHandler::CheckOutSourceDir()
{
  std::string command;
  std::string output;
  int retVal;
Andy Cedilnik's avatar
Andy Cedilnik committed
596
  bool res;
597

Andy Cedilnik's avatar
Andy Cedilnik committed
598
  if (!cmSystemTools::FileExists(this->SourceDir.c_str()) &&
599
      !this->CVSCheckOut.empty()) {
600 601
    // we must now checkout the src dir
    output = "";
Andy Cedilnik's avatar
Andy Cedilnik committed
602
    cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
603
               "Run cvs: " << this->CVSCheckOut << std::endl);
604
    res = cmSystemTools::RunSingleCommand(
605 606 607
      this->CVSCheckOut.c_str(), &output, &output, &retVal,
      this->CTestRoot.c_str(), this->HandlerVerbose, 0 /*this->TimeOut*/);
    if (!res || retVal != 0) {
Andy Cedilnik's avatar
Andy Cedilnik committed
608 609
      cmSystemTools::Error("Unable to perform cvs checkout:\n",
                           output.c_str());
610 611
      return 6;
    }
612
  }
613 614 615 616 617 618 619 620
  return 0;
}

int cmCTestScriptHandler::BackupDirectories()
{
  int retVal;

  // compute the backup names
Andy Cedilnik's avatar
Andy Cedilnik committed
621 622 623 624
  this->BackupSourceDir = this->SourceDir;
  this->BackupSourceDir += "_CMakeBackup";
  this->BackupBinaryDir = this->BinaryDir;
  this->BackupBinaryDir += "_CMakeBackup";
Andy Cedilnik's avatar
Andy Cedilnik committed
625

626
  // backup the binary and src directories if requested
627
  if (this->Backup) {
628
    // if for some reason those directories exist then first delete them
629
    if (cmSystemTools::FileExists(this->BackupSourceDir.c_str())) {
630
      cmSystemTools::RemoveADirectory(this->BackupSourceDir);
631 632
    }
    if (cmSystemTools::FileExists(this->BackupBinaryDir.c_str())) {
633
      cmSystemTools::RemoveADirectory(this->BackupBinaryDir);
634
    }
Andy Cedilnik's avatar
Andy Cedilnik committed
635 636

    // first rename the src and binary directories
Andy Cedilnik's avatar
Andy Cedilnik committed
637 638
    rename(this->SourceDir.c_str(), this->BackupSourceDir.c_str());
    rename(this->BinaryDir.c_str(), this->BackupBinaryDir.c_str());
Andy Cedilnik's avatar
Andy Cedilnik committed
639

640 641
    // we must now checkout the src dir
    retVal = this->CheckOutSourceDir();
642
    if (retVal) {
643 644 645
      this->RestoreBackupDirectories();
      return retVal;
    }
646
  }
647 648 649 650 651 652 653 654 655

  return 0;
}

int cmCTestScriptHandler::PerformExtraUpdates()
{
  std::string command;
  std::string output;
  int retVal;
Andy Cedilnik's avatar
Andy Cedilnik committed
656
  bool res;
657 658

  // do an initial cvs update as required
659
  command = this->UpdateCmd;
660
  std::vector<std::string>::iterator it;
661
  for (it = this->ExtraUpdates.begin(); it != this->ExtraUpdates.end(); ++it) {
662
    std::vector<std::string> cvsArgs;
663 664
    cmSystemTools::ExpandListArgument(*it, cvsArgs);
    if (cvsArgs.size() == 2) {
665 666 667 668 669
      std::string fullCommand = command;
      fullCommand += " update ";
      fullCommand += cvsArgs[1];
      output = "";
      retVal = 0;
670 671
      cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                 "Run Update: " << fullCommand << std::endl);
672
      res = cmSystemTools::RunSingleCommand(
673
        fullCommand.c_str(), &output, &output, &retVal, cvsArgs[0].c_str(),
Andy Cedilnik's avatar
Andy Cedilnik committed
674
        this->HandlerVerbose, 0 /*this->TimeOut*/);
675 676 677
      if (!res || retVal != 0) {
        cmSystemTools::Error("Unable to perform extra updates:\n", it->c_str(),
                             "\nWith output:\n", output.c_str());
678
        return 0;
679 680
      }
    }
681
  }
682 683 684 685 686 687 688 689 690 691
  return 0;
}

// run a single dashboard entry
int cmCTestScriptHandler::RunConfigurationDashboard()
{
  // local variables
  std::string command;
  std::string output;
  int retVal;
Andy Cedilnik's avatar
Andy Cedilnik committed
692
  bool res;
693 694 695 696

  // make sure the src directory is there, if it isn't then we might be able
  // to check it out from cvs
  retVal = this->CheckOutSourceDir();
697
  if (retVal) {
698
    return retVal;
699
  }
700 701 702

  // backup the dirs if requested
  retVal = this->BackupDirectories();
703
  if (retVal) {
704
    return retVal;
705
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
706

707
  // clear the binary directory?
708 709
  if (this->EmptyBinDir) {
    if (!cmCTestScriptHandler::EmptyBinaryDirectory(this->BinaryDir.c_str())) {
Andy Cedilnik's avatar
Andy Cedilnik committed
710
      cmCTestLog(this->CTest, ERROR_MESSAGE,
711
                 "Problem removing the binary directory" << std::endl);
712
    }
713
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
714

715
  // make sure the binary directory exists if it isn't the srcdir
Andy Cedilnik's avatar
Andy Cedilnik committed
716
  if (!cmSystemTools::FileExists(this->BinaryDir.c_str()) &&
717 718
      this->SourceDir != this->BinaryDir) {
    if (!cmSystemTools::MakeDirectory(this->BinaryDir.c_str())) {
Andy Cedilnik's avatar
Andy Cedilnik committed
719
      cmSystemTools::Error("Unable to create the binary directory:\n",
Andy Cedilnik's avatar
Andy Cedilnik committed
720
                           this->BinaryDir.c_str());
721 722 723
      this->RestoreBackupDirectories();
      return 7;
    }
724
  }
725 726 727 728

  // if the binary directory and the source directory are the same,
  // and we are starting with an empty binary directory, then that means
  // we must check out the source tree
729
  if (this->EmptyBinDir && this->SourceDir == this->BinaryDir) {
730
    // make sure we have the required info
731 732 733
    if (this->CVSCheckOut.empty()) {
      cmSystemTools::Error(
        "You have specified the source and binary "
Andy Cedilnik's avatar
Andy Cedilnik committed
734 735 736 737
        "directories to be the same (an in source build). You have also "
        "specified that the binary directory is to be erased. This means "
        "that the source will have to be checked out from CVS. But you have "
        "not specified CTEST_CVS_CHECKOUT");
738
      return 8;
739
    }
Andy Cedilnik's avatar
Andy Cedilnik committed
740

741 742
    // we must now checkout the src dir
    retVal = this->CheckOutSourceDir();
743
    if (retVal) {
744 745 746
      this->RestoreBackupDirectories();
      return retVal;
    }
747
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
748

749 750
  // backup the dirs if requested
  retVal = this->PerformExtraUpdates();
751
  if (retVal) {
752
    return retVal;
753
  }
754 755

  // put the initial cache into the bin dir
756
  if (!this->InitialCache.empty()) {
Brad King's avatar
Brad King committed
757
    if (!this->WriteInitialCache(this->BinaryDir.c_str(),
758
                                 this->InitialCache.c_str())) {
759 760 761
      this->RestoreBackupDirectories();
      return 9;
    }
762
  }
763 764 765 766

  // do an initial cmake to setup the DartConfig file
  int cmakeFailed = 0;
  std::string cmakeFailedOuput;
767
  if (!this->CMakeCmd.empty()) {
Andy Cedilnik's avatar
Andy Cedilnik committed
768
    command = this->CMakeCmd;
769
    command += " \"";
Andy Cedilnik's avatar
Andy Cedilnik committed
770
    command += this->SourceDir;
771 772 773
    output = "";
    command += "\"";
    retVal = 0;
774 775
    cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
               "Run cmake command: " << command << std::endl);
776
    res = cmSystemTools::RunSingleCommand(
777
      command.c_str(), &output, &output, &retVal, this->BinaryDir.c_str(),
Andy Cedilnik's avatar
Andy Cedilnik committed
778
      this->HandlerVerbose, 0 /*this->TimeOut*/);
Andy Cedilnik's avatar
Andy Cedilnik committed
779

780
    if (!this->CMOutFile.empty()) {
Andy Cedilnik's avatar
Andy Cedilnik committed
781
      std::string cmakeOutputFile = this->CMOutFile;
782
      if (!cmSystemTools::FileIsFullPath(cmakeOutputFile.c_str())) {
Andy Cedilnik's avatar
Andy Cedilnik committed
783
        cmakeOutputFile = this->BinaryDir + "/" + cmakeOutputFile;
784
      }
Andy Cedilnik's avatar
Andy Cedilnik committed
785

Andy Cedilnik's avatar
Andy Cedilnik committed
786
      cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
787 788
                 "Write CMake output to file: " << cmakeOutputFile
                                                << std::endl);
789
      cmGeneratedFileStream fout(cmakeOutputFile.c_str());
790
      if (fout) {
Andy Cedilnik's avatar
Andy Cedilnik committed
791
        fout << output.c_str();
792
      } else {
Andy Cedilnik's avatar
Andy Cedilnik committed
793
        cmCTestLog(this->CTest, ERROR_MESSAGE,
794 795
                   "Cannot open CMake output file: "
                     << cmakeOutputFile << " for writing" << std::endl);
Andy Cedilnik's avatar
Andy Cedilnik committed
796
      }
797 798
    }
    if (!res || retVal != 0) {
799 800 801 802
      // even if this fails continue to the next step
      cmakeFailed = 1;
      cmakeFailedOuput = output;
    }
803
  }
804 805 806

  // run ctest, it may be more than one command in here
  std::vector<std::string> ctestCommands;
807
  cmSystemTools::ExpandListArgument(this->CTestCmd, ctestCommands);
808
  // for each variable/argument do a putenv
809
  for (unsigned i = 0; i < ctestCommands.size(); ++i) {
810 811 812
    command = ctestCommands[i];
    output = "";
    retVal = 0;
813 814
    cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
               "Run ctest command: " << command << std::endl);
815
    res = cmSystemTools::RunSingleCommand(
816 817
      command.c_str(), &output, &output, &retVal, this->BinaryDir.c_str(),
      this->HandlerVerbose, 0 /*this->TimeOut*/);
Andy Cedilnik's avatar
Andy Cedilnik committed
818

819
    // did something critical fail in ctest
820
    if (!res || cmakeFailed || retVal & cmCTest::BUILD_ERRORS) {
821
      this->RestoreBackupDirectories();
822
      if (cmakeFailed) {
Andy Cedilnik's avatar
Andy Cedilnik committed
823
        cmCTestLog(this->CTest, ERROR_MESSAGE,
824 825
                   "Unable to run cmake:" << std::endl
                                          << cmakeFailedOuput << std::endl);
826
        return 10;
827
      }
Andy Cedilnik's avatar
Andy Cedilnik committed
828
      cmCTestLog(this->CTest, ERROR_MESSAGE,
829 830 831 832
                 "Unable to run ctest:" << std::endl
                                        << "command: " << command << std::endl
                                        << "output: " << output << std::endl);
      if (!res) {
833 834
        return 11;
      }
835
      return retVal * 100;
836
    }
837
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
838

839
  // if all was succesful, delete the backup dirs to free up disk space
840
  if (this->Backup) {
841 842
    cmSystemTools::RemoveADirectory(this->BackupSourceDir);
    cmSystemTools::RemoveADirectory(this->BackupBinaryDir);
843
  }
844

Andy Cedilnik's avatar
Andy Cedilnik committed
845
  return 0;
846 847
}

Brad King's avatar
Brad King committed
848
bool cmCTestScriptHandler::WriteInitialCache(const char* directory,
849 850 851 852 853
                                             const char* text)
{
  std::string cacheFile = directory;
  cacheFile += "/CMakeCache.txt";
  cmGeneratedFileStream fout(cacheFile.c_str());
854
  if (!fout) {