| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #include "cmExecuteProcessCommand.h" |
| |
| #include "cmsys/Process.h" |
| #include <ctype.h> /* isspace */ |
| #include <sstream> |
| #include <stdio.h> |
| |
| #include "cmAlgorithms.h" |
| #include "cmMakefile.h" |
| #include "cmProcessOutput.h" |
| #include "cmSystemTools.h" |
| |
| class cmExecutionStatus; |
| |
| static bool cmExecuteProcessCommandIsWhitespace(char c) |
| { |
| return (isspace(static_cast<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.empty()) { |
| 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 results_variable; |
| std::string working_directory; |
| cmProcessOutput::Encoding encoding = cmProcessOutput::None; |
| 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] == "RESULTS_VARIABLE") { |
| doing_command = false; |
| if (++i < args.size()) { |
| results_variable = args[i]; |
| } else { |
| this->SetError(" called with no value for RESULTS_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 (args[i] == "ENCODING") { |
| doing_command = false; |
| if (++i < args.size()) { |
| encoding = cmProcessOutput::FindEncoding(args[i]); |
| } else { |
| this->SetError(" called with no value for ENCODING."); |
| return false; |
| } |
| } else if (doing_command) { |
| cmds[command_index].push_back(args[i].c_str()); |
| } else { |
| std::ostringstream e; |
| e << " given unknown argument \"" << args[i] << "\"."; |
| this->SetError(e.str()); |
| return false; |
| } |
| } |
| |
| if (!this->Makefile->CanIWriteThisFile(output_file)) { |
| std::string e = "attempted to output into a file: " + output_file + |
| " into a source directory."; |
| this->SetError(e); |
| cmSystemTools::SetFatalErrorOccured(); |
| return false; |
| } |
| |
| // Check for commands given. |
| if (cmds.empty()) { |
| this->SetError(" called with no COMMAND argument."); |
| return false; |
| } |
| for (auto& cmd : cmds) { |
| if (cmd.empty()) { |
| this->SetError(" given COMMAND argument with no value."); |
| return false; |
| } |
| // Add the null terminating pointer to the command argument list. |
| cmd.push_back(nullptr); |
| } |
| |
| // 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 (auto const& cmd : cmds) { |
| cmsysProcess_AddCommand(cp, &*cmd.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 = false; |
| 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()) { |
| if (error_file == output_file) { |
| merge_output = true; |
| } else { |
| cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDERR, |
| error_file.c_str()); |
| } |
| } |
| if (!output_variable.empty() && output_variable == error_variable) { |
| merge_output = true; |
| } |
| if (merge_output) { |
| cmsysProcess_SetOption(cp, cmsysProcess_Option_MergeOutput, 1); |
| } |
| |
| // 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; |
| cmProcessOutput processOutput(encoding); |
| std::string strdata; |
| while ((p = cmsysProcess_WaitForData(cp, &data, &length, nullptr), p)) { |
| // Put the output in the right place. |
| if (p == cmsysProcess_Pipe_STDOUT && !output_quiet) { |
| if (output_variable.empty()) { |
| processOutput.DecodeText(data, length, strdata, 1); |
| cmSystemTools::Stdout(strdata.c_str(), strdata.size()); |
| } else { |
| cmExecuteProcessCommandAppend(tempOutput, data, length); |
| } |
| } else if (p == cmsysProcess_Pipe_STDERR && !error_quiet) { |
| if (error_variable.empty()) { |
| processOutput.DecodeText(data, length, strdata, 2); |
| cmSystemTools::Stderr(strdata.c_str(), strdata.size()); |
| } else { |
| cmExecuteProcessCommandAppend(tempError, data, length); |
| } |
| } |
| } |
| if (!output_quiet && output_variable.empty()) { |
| processOutput.DecodeText(std::string(), strdata, 1); |
| if (!strdata.empty()) { |
| cmSystemTools::Stdout(strdata.c_str(), strdata.size()); |
| } |
| } |
| if (!error_quiet && error_variable.empty()) { |
| processOutput.DecodeText(std::string(), strdata, 2); |
| if (!strdata.empty()) { |
| cmSystemTools::Stderr(strdata.c_str(), strdata.size()); |
| } |
| } |
| |
| // All output has been read. Wait for the process to exit. |
| cmsysProcess_WaitForExit(cp, nullptr); |
| processOutput.DecodeText(tempOutput, tempOutput); |
| processOutput.DecodeText(tempError, tempError); |
| |
| // 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.empty()) { |
| this->Makefile->AddDefinition(output_variable, &*tempOutput.begin()); |
| } |
| if (!merge_output && !error_variable.empty() && !tempError.empty()) { |
| this->Makefile->AddDefinition(error_variable, &*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[16]; |
| sprintf(buf, "%d", v); |
| this->Makefile->AddDefinition(result_variable, buf); |
| } break; |
| case cmsysProcess_State_Exception: |
| this->Makefile->AddDefinition(result_variable, |
| cmsysProcess_GetExceptionString(cp)); |
| break; |
| case cmsysProcess_State_Error: |
| this->Makefile->AddDefinition(result_variable, |
| cmsysProcess_GetErrorString(cp)); |
| break; |
| case cmsysProcess_State_Expired: |
| this->Makefile->AddDefinition(result_variable, |
| "Process terminated due to timeout"); |
| break; |
| } |
| } |
| // Store the result of running the processes. |
| if (!results_variable.empty()) { |
| switch (cmsysProcess_GetState(cp)) { |
| case cmsysProcess_State_Exited: { |
| std::vector<std::string> res; |
| for (size_t i = 0; i < cmds.size(); ++i) { |
| switch (cmsysProcess_GetStateByIndex(cp, static_cast<int>(i))) { |
| case kwsysProcess_StateByIndex_Exited: { |
| int exitCode = |
| cmsysProcess_GetExitValueByIndex(cp, static_cast<int>(i)); |
| char buf[16]; |
| sprintf(buf, "%d", exitCode); |
| res.push_back(buf); |
| } break; |
| case kwsysProcess_StateByIndex_Exception: |
| res.push_back(cmsysProcess_GetExceptionStringByIndex( |
| cp, static_cast<int>(i))); |
| break; |
| case kwsysProcess_StateByIndex_Error: |
| default: |
| res.push_back("Error getting the child return code"); |
| break; |
| } |
| } |
| this->Makefile->AddDefinition(results_variable, |
| cmJoin(res, ";").c_str()); |
| } break; |
| case cmsysProcess_State_Exception: |
| this->Makefile->AddDefinition(results_variable, |
| cmsysProcess_GetExceptionString(cp)); |
| break; |
| case cmsysProcess_State_Error: |
| this->Makefile->AddDefinition(results_variable, |
| cmsysProcess_GetErrorString(cp)); |
| break; |
| case cmsysProcess_State_Expired: |
| this->Makefile->AddDefinition(results_variable, |
| "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); |
| } |