/* 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 /* isspace */ #include #include #include "cmAlgorithms.h" #include "cmMakefile.h" #include "cmProcessOutput.h" #include "cmSystemTools.h" class cmExecutionStatus; static bool cmExecuteProcessCommandIsWhitespace(char c) { return (isspace(static_cast(c)) || c == '\n' || c == '\r'); } void cmExecuteProcessCommandFixText(std::vector& output, bool strip_trailing_whitespace); void cmExecuteProcessCommandAppend(std::vector& output, const char* data, int length); // cmExecuteProcessCommand bool cmExecuteProcessCommand::InitialPass(std::vector const& args, cmExecutionStatus&) { if (args.empty()) { this->SetError("called with incorrect number of arguments"); return false; } std::vector> 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.emplace_back(); } 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 tempOutput; std::vector 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 res; for (size_t i = 0; i < cmds.size(); ++i) { switch (cmsysProcess_GetStateByIndex(cp, static_cast(i))) { case kwsysProcess_StateByIndex_Exited: { int exitCode = cmsysProcess_GetExitValueByIndex(cp, static_cast(i)); char buf[16]; sprintf(buf, "%d", exitCode); res.emplace_back(buf); } break; case kwsysProcess_StateByIndex_Exception: res.emplace_back(cmsysProcess_GetExceptionStringByIndex( cp, static_cast(i))); break; case kwsysProcess_StateByIndex_Error: default: res.emplace_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& 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& 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); }