From 2683c5bd0d74c01ef8ab2bd60a00b5e9115c1d65 Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 3 Feb 2006 16:51:46 -0500 Subject: [PATCH] ENH: Adding new EXECUTE_PROCESS command that interfaces to KWSys Process Execution. --- Source/cmCommands.cxx | 2 + Source/cmExecuteProcessCommand.cxx | 335 +++++++++++++++++++++++++++++ Source/cmExecuteProcessCommand.h | 113 ++++++++++ 3 files changed, 450 insertions(+) create mode 100644 Source/cmExecuteProcessCommand.cxx create mode 100644 Source/cmExecuteProcessCommand.h diff --git a/Source/cmCommands.cxx b/Source/cmCommands.cxx index ec989d4ea..88b06c252 100644 --- a/Source/cmCommands.cxx +++ b/Source/cmCommands.cxx @@ -18,6 +18,7 @@ #if defined(CMAKE_BUILD_WITH_CMAKE) #include "cmAuxSourceDirectoryCommand.cxx" #include "cmEndWhileCommand.cxx" +#include "cmExecuteProcessCommand.cxx" #include "cmExportLibraryDependencies.cxx" #include "cmEnableLanguageCommand.cxx" #include "cmFLTKWrapUICommand.cxx" @@ -60,6 +61,7 @@ void GetPredefinedCommands(std::list& commands.push_back(new cmAuxSourceDirectoryCommand); commands.push_back(new cmEnableLanguageCommand); commands.push_back(new cmEndWhileCommand); + commands.push_back(new cmExecuteProcessCommand); commands.push_back(new cmExportLibraryDependenciesCommand); commands.push_back(new cmFLTKWrapUICommand); commands.push_back(new cmGetCMakePropertyCommand); diff --git a/Source/cmExecuteProcessCommand.cxx b/Source/cmExecuteProcessCommand.cxx new file mode 100644 index 000000000..64835fe86 --- /dev/null +++ b/Source/cmExecuteProcessCommand.cxx @@ -0,0 +1,335 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. + See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#include "cmExecuteProcessCommand.h" +#include "cmSystemTools.h" + +#include + +// cmExecuteProcessCommand +bool cmExecuteProcessCommand::InitialPass(std::vector const& args) +{ + if(args.size() < 1 ) + { + this->SetError("called with incorrect number of arguments"); + return false; + } + std::vector< std::vector > cmds; + std::string arguments; + bool doing_command = false; + unsigned int command_index = 0; + bool output_quiet = false; + bool error_quiet = 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()); + } + 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(doing_command) + { + cmds[command_index].push_back(args[i].c_str()); + } + } + + // 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 tempOutput; + std::vector tempError; + int length; + char* data; + int p; + while((p = cmsysProcess_WaitForData(cp, &data, &length, 0), p)) + { + // Translate NULL characters in the output into valid text. + for(int i=0; i < length; ++i) + { + if(data[i] == '\0') + { + data[i] = ' '; + } + } + + // 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 + { + tempOutput.insert(tempOutput.end(), data, data+length); + } + } + else if(p == cmsysProcess_Pipe_STDERR && !error_quiet) + { + if(!error_variable.empty()) + { + tempError.insert(tempError.end(), data, data+length); + } + } + } + + // All output has been read. Wait for the process to exit. + cmsysProcess_WaitForExit(cp, 0); + + // Store the output obtained. + if(!output_variable.empty()) + { + tempOutput.push_back('\0'); + m_Makefile->AddDefinition(output_variable.c_str(), &*tempOutput.begin()); + } + if(!merge_output && !error_variable.empty()) + { + tempError.push_back('\0'); + m_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); + m_Makefile->AddDefinition(result_variable.c_str(), buf); + } + break; + case cmsysProcess_State_Exception: + m_Makefile->AddDefinition(result_variable.c_str(), + cmsysProcess_GetExceptionString(cp)); + break; + case cmsysProcess_State_Error: + m_Makefile->AddDefinition(result_variable.c_str(), + cmsysProcess_GetErrorString(cp)); + break; + case cmsysProcess_State_Expired: + m_Makefile->AddDefinition(result_variable.c_str(), + "Process terminated due to timeout"); + break; + } + } + + // Delete the process instance. + cmsysProcess_Delete(cp); + + return true; +} diff --git a/Source/cmExecuteProcessCommand.h b/Source/cmExecuteProcessCommand.h new file mode 100644 index 000000000..36ea044f3 --- /dev/null +++ b/Source/cmExecuteProcessCommand.h @@ -0,0 +1,113 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. + See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#ifndef cmExecuteProcessCommand_h +#define cmExecuteProcessCommand_h + +#include "cmCommand.h" + +/** \class cmExecuteProcessCommand + * \brief Command that adds a target to the build system. + * + * cmExecuteProcessCommand is a CMake language interface to the KWSys + * Process Execution implementation. + */ +class cmExecuteProcessCommand : public cmCommand +{ +public: + /** + * This is a virtual constructor for the command. + */ + virtual cmCommand* Clone() + { + return new cmExecuteProcessCommand; + } + + /** + * This is called when the command is first encountered in + * the CMakeLists.txt file. + */ + virtual bool InitialPass(std::vector const& args); + + /** + * The name of the command as specified in CMakeList.txt. + */ + virtual const char* GetName() + {return "EXECUTE_PROCESS";} + + /** + * This determines if the command is invoked when in script mode. + */ + virtual bool IsScriptable() { return true; } + + /** + * Succinct documentation. + */ + virtual const char* GetTerseDocumentation() + { + return "Execute one or more child processes."; + } + + /** + * More documentation. + */ + virtual const char* GetFullDocumentation() + { + return + " EXECUTE_PROCESS(COMMAND [args1...]]\n" + " [COMMAND [args2...] [...]]\n" + " [WORKING_DIRECTORY ]\n" + " [TIMEOUT ]\n" + " [RESULT_VARIABLE ]\n" + " [OUTPUT_VARIABLE ]\n" + " [ERROR_VARIABLE ]\n" + " [INPUT_FILE ]\n" + " [OUTPUT_FILE ]\n" + " [ERROR_FILE ]\n" + " [OUTPUT_QUIET]\n" + " [ERROR_QUIET])\n" + "Runs the given sequence of one or more commands with the standard " + "output of each process piped to the standard input of the next. " + "A single standard error pipe is used for all processes. " + "If WORKING_DIRECTORY is given the named directory will be set as " + "the current working directory of the child processes. " + "If TIMEOUT is given the child processes will be terminated if they " + "do not finish in the specified number of seconds " + "(fractions are allowed). " + "If RESULT_VARIABLE is given the variable will be set to contain " + "the result of running the processes. This will be an integer return " + "code from the last child or a string describing an error condition. " + "If OUTPUT_VARIABLE or ERROR_VARIABLE are given the variable named " + "will be set with the contents of the standard output and standard error " + "pipes respectively. If the same variable is named for both pipes " + "their output will be merged in the order produced. " + "If INPUT_FILE, OUTPUT_FILE, or ERROR_FILE is given the file named " + "will be attached to the standard input of the first process, " + "standard output of the last process, or standard error of all " + "processes respectively. " + "If OUTPUT_QUIET or ERROR_QUIET is given then the standard output " + "or standard error results will be quietly ignored. " + "If more than one OUTPUT_* or ERROR_* option is given for the same " + "pipe the precedence is not specified. " + "If no OUTPUT_* or ERROR_* options are given the output will be shared " + "with the corresponding pipes of the CMake process itself." + ; + } + + cmTypeMacro(cmExecuteProcessCommand, cmCommand); +}; + +#endif