/*============================================================================ CMake - Cross Platform Makefile Generator Copyright 2000-2009 Kitware, Inc., Insight Software Consortium Distributed under the OSI-approved BSD License (the "License"); see accompanying file Copyright.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more information. ============================================================================*/ #include "cmExecuteProcessCommand.h" #include "cmSystemTools.h" #include <cmsys/Process.h> #include <ctype.h> /* isspace */ static bool cmExecuteProcessCommandIsWhitespace(char c) { return (isspace((int)c) || c == '\n' || c == '\r'); } void cmExecuteProcessCommandFixText(std::vector<char>& output, bool strip_trailing_whitespace); void cmExecuteProcessCommandAppend(std::vector<char>& output, const char* data, int length); // cmExecuteProcessCommand bool cmExecuteProcessCommand ::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &) { if(args.size() < 1 ) { this->SetError("called with incorrect number of arguments"); return false; } std::vector< std::vector<const char*> > cmds; std::string arguments; bool doing_command = false; size_t command_index = 0; bool output_quiet = false; bool error_quiet = false; bool output_strip_trailing_whitespace = false; bool error_strip_trailing_whitespace = false; std::string timeout_string; std::string input_file; std::string output_file; std::string error_file; std::string output_variable; std::string error_variable; std::string result_variable; std::string working_directory; for(size_t i=0; i < args.size(); ++i) { if(args[i] == "COMMAND") { doing_command = true; command_index = cmds.size(); cmds.push_back(std::vector<const char*>()); } else if(args[i] == "OUTPUT_VARIABLE") { doing_command = false; if(++i < args.size()) { output_variable = args[i]; } else { this->SetError(" called with no value for OUTPUT_VARIABLE."); return false; } } else if(args[i] == "ERROR_VARIABLE") { doing_command = false; if(++i < args.size()) { error_variable = args[i]; } else { this->SetError(" called with no value for ERROR_VARIABLE."); return false; } } else if(args[i] == "RESULT_VARIABLE") { doing_command = false; if(++i < args.size()) { result_variable = args[i]; } else { this->SetError(" called with no value for RESULT_VARIABLE."); return false; } } else if(args[i] == "WORKING_DIRECTORY") { doing_command = false; if(++i < args.size()) { working_directory = args[i]; } else { this->SetError(" called with no value for WORKING_DIRECTORY."); return false; } } else if(args[i] == "INPUT_FILE") { doing_command = false; if(++i < args.size()) { input_file = args[i]; } else { this->SetError(" called with no value for INPUT_FILE."); return false; } } else if(args[i] == "OUTPUT_FILE") { doing_command = false; if(++i < args.size()) { output_file = args[i]; } else { this->SetError(" called with no value for OUTPUT_FILE."); return false; } } else if(args[i] == "ERROR_FILE") { doing_command = false; if(++i < args.size()) { error_file = args[i]; } else { this->SetError(" called with no value for ERROR_FILE."); return false; } } else if(args[i] == "TIMEOUT") { doing_command = false; if(++i < args.size()) { timeout_string = args[i]; } else { this->SetError(" called with no value for TIMEOUT."); return false; } } else if(args[i] == "OUTPUT_QUIET") { doing_command = false; output_quiet = true; } else if(args[i] == "ERROR_QUIET") { doing_command = false; error_quiet = true; } else if(args[i] == "OUTPUT_STRIP_TRAILING_WHITESPACE") { doing_command = false; output_strip_trailing_whitespace = true; } else if(args[i] == "ERROR_STRIP_TRAILING_WHITESPACE") { doing_command = false; error_strip_trailing_whitespace = true; } else if(doing_command) { cmds[command_index].push_back(args[i].c_str()); } else { cmOStringStream e; e << " given unknown argument \"" << args[i] << "\"."; this->SetError(e.str().c_str()); return false; } } if ( !this->Makefile->CanIWriteThisFile(output_file.c_str()) ) { std::string e = "attempted to output into a file: " + output_file + " into a source directory."; this->SetError(e.c_str()); cmSystemTools::SetFatalErrorOccured(); return false; } // Check for commands given. if(cmds.empty()) { this->SetError(" called with no COMMAND argument."); return false; } for(unsigned int i=0; i < cmds.size(); ++i) { if(cmds[i].empty()) { this->SetError(" given COMMAND argument with no value."); return false; } else { // Add the null terminating pointer to the command argument list. cmds[i].push_back(0); } } // Parse the timeout string. double timeout = -1; if(!timeout_string.empty()) { if(sscanf(timeout_string.c_str(), "%lg", &timeout) != 1) { this->SetError(" called with TIMEOUT value that could not be parsed."); return false; } } // Create a process instance. cmsysProcess* cp = cmsysProcess_New(); // Set the command sequence. for(unsigned int i=0; i < cmds.size(); ++i) { cmsysProcess_AddCommand(cp, &*cmds[i].begin()); } // Set the process working directory. if(!working_directory.empty()) { cmsysProcess_SetWorkingDirectory(cp, working_directory.c_str()); } // Always hide the process window. cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1); // Check the output variables. bool merge_output = (output_variable == error_variable); if(error_variable.empty() && !error_quiet) { cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDERR, 1); } if(!input_file.empty()) { cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDIN, input_file.c_str()); } if(!output_file.empty()) { cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDOUT, output_file.c_str()); } if(!error_file.empty()) { cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDERR, error_file.c_str()); } // Set the timeout if any. if(timeout >= 0) { cmsysProcess_SetTimeout(cp, timeout); } // Start the process. cmsysProcess_Execute(cp); // Read the process output. std::vector<char> tempOutput; std::vector<char> tempError; int length; char* data; int p; while((p = cmsysProcess_WaitForData(cp, &data, &length, 0), p)) { // Put the output in the right place. if((p == cmsysProcess_Pipe_STDOUT && !output_quiet) || (p == cmsysProcess_Pipe_STDERR && !error_quiet && merge_output)) { if(output_variable.empty()) { cmSystemTools::Stdout(data, length); } else { cmExecuteProcessCommandAppend(tempOutput, data, length); } } else if(p == cmsysProcess_Pipe_STDERR && !error_quiet) { if(!error_variable.empty()) { cmExecuteProcessCommandAppend(tempError, data, length); } } } // All output has been read. Wait for the process to exit. cmsysProcess_WaitForExit(cp, 0); // Fix the text in the output strings. cmExecuteProcessCommandFixText(tempOutput, output_strip_trailing_whitespace); cmExecuteProcessCommandFixText(tempError, error_strip_trailing_whitespace); // Store the output obtained. if(!output_variable.empty() && tempOutput.size()) { this->Makefile->AddDefinition(output_variable.c_str(), &*tempOutput.begin()); } if(!merge_output && !error_variable.empty() && tempError.size()) { this->Makefile->AddDefinition(error_variable.c_str(), &*tempError.begin()); } // Store the result of running the process. if(!result_variable.empty()) { switch(cmsysProcess_GetState(cp)) { case cmsysProcess_State_Exited: { int v = cmsysProcess_GetExitValue(cp); char buf[100]; sprintf(buf, "%d", v); this->Makefile->AddDefinition(result_variable.c_str(), buf); } break; case cmsysProcess_State_Exception: this->Makefile->AddDefinition(result_variable.c_str(), cmsysProcess_GetExceptionString(cp)); break; case cmsysProcess_State_Error: this->Makefile->AddDefinition(result_variable.c_str(), cmsysProcess_GetErrorString(cp)); break; case cmsysProcess_State_Expired: this->Makefile->AddDefinition(result_variable.c_str(), "Process terminated due to timeout"); break; } } // Delete the process instance. cmsysProcess_Delete(cp); return true; } //---------------------------------------------------------------------------- void cmExecuteProcessCommandFixText(std::vector<char>& output, bool strip_trailing_whitespace) { // Remove \0 characters and the \r part of \r\n pairs. unsigned int in_index = 0; unsigned int out_index = 0; while(in_index < output.size()) { char c = output[in_index++]; if((c != '\r' || !(in_index < output.size() && output[in_index] == '\n')) && c != '\0') { output[out_index++] = c; } } // Remove trailing whitespace if requested. if(strip_trailing_whitespace) { while(out_index > 0 && cmExecuteProcessCommandIsWhitespace(output[out_index-1])) { --out_index; } } // Shrink the vector to the size needed. output.resize(out_index); // Put a terminator on the text string. output.push_back('\0'); } //---------------------------------------------------------------------------- void cmExecuteProcessCommandAppend(std::vector<char>& output, const char* data, int length) { #if defined(__APPLE__) // HACK on Apple to work around bug with inserting at the // end of an empty vector. This resulted in random failures // that were hard to reproduce. if(output.empty() && length > 0) { output.push_back(data[0]); ++data; --length; } #endif output.insert(output.end(), data, data+length); }