summaryrefslogtreecommitdiffstats
path: root/Source/cmExecuteProcessCommand.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'Source/cmExecuteProcessCommand.cxx')
-rw-r--r--Source/cmExecuteProcessCommand.cxx527
1 files changed, 527 insertions, 0 deletions
diff --git a/Source/cmExecuteProcessCommand.cxx b/Source/cmExecuteProcessCommand.cxx
new file mode 100644
index 0000000..ffcc415
--- /dev/null
+++ b/Source/cmExecuteProcessCommand.cxx
@@ -0,0 +1,527 @@
+/* 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 <algorithm>
+#include <cctype> /* isspace */
+#include <cstdio>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+#include <cm/string_view>
+#include <cmext/algorithm>
+#include <cmext/string_view>
+
+#include "cmsys/Process.h"
+
+#include "cmArgumentParser.h"
+#include "cmExecutionStatus.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmProcessOutput.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+
+namespace {
+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(std::vector<std::string> const& args,
+ cmExecutionStatus& status)
+{
+ if (args.empty()) {
+ status.SetError("called with incorrect number of arguments");
+ return false;
+ }
+
+ struct Arguments
+ {
+ std::vector<std::vector<std::string>> Commands;
+ std::string OutputVariable;
+ std::string ErrorVariable;
+ std::string ResultVariable;
+ std::string ResultsVariable;
+ std::string WorkingDirectory;
+ std::string InputFile;
+ std::string OutputFile;
+ std::string ErrorFile;
+ std::string Timeout;
+ std::string CommandEcho;
+ bool OutputQuiet = false;
+ bool ErrorQuiet = false;
+ bool OutputStripTrailingWhitespace = false;
+ bool ErrorStripTrailingWhitespace = false;
+ bool EchoOutputVariable = false;
+ bool EchoErrorVariable = false;
+ std::string Encoding;
+ std::string CommandErrorIsFatal;
+ };
+
+ static auto const parser =
+ cmArgumentParser<Arguments>{}
+ .Bind("COMMAND"_s, &Arguments::Commands)
+ .Bind("COMMAND_ECHO"_s, &Arguments::CommandEcho)
+ .Bind("OUTPUT_VARIABLE"_s, &Arguments::OutputVariable)
+ .Bind("ERROR_VARIABLE"_s, &Arguments::ErrorVariable)
+ .Bind("RESULT_VARIABLE"_s, &Arguments::ResultVariable)
+ .Bind("RESULTS_VARIABLE"_s, &Arguments::ResultsVariable)
+ .Bind("WORKING_DIRECTORY"_s, &Arguments::WorkingDirectory)
+ .Bind("INPUT_FILE"_s, &Arguments::InputFile)
+ .Bind("OUTPUT_FILE"_s, &Arguments::OutputFile)
+ .Bind("ERROR_FILE"_s, &Arguments::ErrorFile)
+ .Bind("TIMEOUT"_s, &Arguments::Timeout)
+ .Bind("OUTPUT_QUIET"_s, &Arguments::OutputQuiet)
+ .Bind("ERROR_QUIET"_s, &Arguments::ErrorQuiet)
+ .Bind("OUTPUT_STRIP_TRAILING_WHITESPACE"_s,
+ &Arguments::OutputStripTrailingWhitespace)
+ .Bind("ERROR_STRIP_TRAILING_WHITESPACE"_s,
+ &Arguments::ErrorStripTrailingWhitespace)
+ .Bind("ENCODING"_s, &Arguments::Encoding)
+ .Bind("ECHO_OUTPUT_VARIABLE"_s, &Arguments::EchoOutputVariable)
+ .Bind("ECHO_ERROR_VARIABLE"_s, &Arguments::EchoErrorVariable)
+ .Bind("COMMAND_ERROR_IS_FATAL"_s, &Arguments::CommandErrorIsFatal);
+
+ std::vector<std::string> unparsedArguments;
+ std::vector<std::string> keywordsMissingValue;
+ Arguments const arguments =
+ parser.Parse(args, &unparsedArguments, &keywordsMissingValue);
+
+ if (!keywordsMissingValue.empty()) {
+ status.SetError(" called with no value for " +
+ keywordsMissingValue.front() + ".");
+ return false;
+ }
+ if (!unparsedArguments.empty()) {
+ status.SetError(" given unknown argument \"" + unparsedArguments.front() +
+ "\".");
+ return false;
+ }
+
+ if (!status.GetMakefile().CanIWriteThisFile(arguments.OutputFile)) {
+ status.SetError("attempted to output into a file: " +
+ arguments.OutputFile + " into a source directory.");
+ cmSystemTools::SetFatalErrorOccured();
+ return false;
+ }
+
+ // Check for commands given.
+ if (arguments.Commands.empty()) {
+ status.SetError(" called with no COMMAND argument.");
+ return false;
+ }
+ for (std::vector<std::string> const& cmd : arguments.Commands) {
+ if (cmd.empty()) {
+ status.SetError(" given COMMAND argument with no value.");
+ return false;
+ }
+ }
+
+ // Parse the timeout string.
+ double timeout = -1;
+ if (!arguments.Timeout.empty()) {
+ if (sscanf(arguments.Timeout.c_str(), "%lg", &timeout) != 1) {
+ status.SetError(" called with TIMEOUT value that could not be parsed.");
+ return false;
+ }
+ }
+
+ if (!arguments.CommandErrorIsFatal.empty()) {
+ if (arguments.CommandErrorIsFatal != "ANY"_s &&
+ arguments.CommandErrorIsFatal != "LAST"_s) {
+ status.SetError("COMMAND_ERROR_IS_FATAL option can be ANY or LAST");
+ return false;
+ }
+ }
+ // Create a process instance.
+ std::unique_ptr<cmsysProcess, void (*)(cmsysProcess*)> cp_ptr(
+ cmsysProcess_New(), cmsysProcess_Delete);
+ cmsysProcess* cp = cp_ptr.get();
+
+ // Set the command sequence.
+ for (std::vector<std::string> const& cmd : arguments.Commands) {
+ std::vector<const char*> argv(cmd.size() + 1);
+ std::transform(cmd.begin(), cmd.end(), argv.begin(),
+ [](std::string const& s) { return s.c_str(); });
+ argv.back() = nullptr;
+ cmsysProcess_AddCommand(cp, argv.data());
+ }
+
+ // Set the process working directory.
+ if (!arguments.WorkingDirectory.empty()) {
+ cmsysProcess_SetWorkingDirectory(cp, arguments.WorkingDirectory.c_str());
+ }
+
+ // Always hide the process window.
+ cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
+
+ // Check the output variables.
+ bool merge_output = false;
+ if (!arguments.InputFile.empty()) {
+ cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDIN,
+ arguments.InputFile.c_str());
+ }
+ if (!arguments.OutputFile.empty()) {
+ cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDOUT,
+ arguments.OutputFile.c_str());
+ }
+ if (!arguments.ErrorFile.empty()) {
+ if (arguments.ErrorFile == arguments.OutputFile) {
+ merge_output = true;
+ } else {
+ cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDERR,
+ arguments.ErrorFile.c_str());
+ }
+ }
+ if (!arguments.OutputVariable.empty() &&
+ arguments.OutputVariable == arguments.ErrorVariable) {
+ 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);
+ }
+
+ bool echo_stdout = false;
+ bool echo_stderr = false;
+ bool echo_output_from_variable = true;
+ std::string echo_output = status.GetMakefile().GetSafeDefinition(
+ "CMAKE_EXECUTE_PROCESS_COMMAND_ECHO");
+ if (!arguments.CommandEcho.empty()) {
+ echo_output_from_variable = false;
+ echo_output = arguments.CommandEcho;
+ }
+
+ if (!echo_output.empty()) {
+ if (echo_output == "STDERR") {
+ echo_stderr = true;
+ } else if (echo_output == "STDOUT") {
+ echo_stdout = true;
+ } else if (echo_output != "NONE") {
+ std::string error;
+ if (echo_output_from_variable) {
+ error = "CMAKE_EXECUTE_PROCESS_COMMAND_ECHO set to '";
+ } else {
+ error = " called with '";
+ }
+ error += echo_output;
+ error += "' expected STDERR|STDOUT|NONE";
+ if (!echo_output_from_variable) {
+ error += " for COMMAND_ECHO.";
+ }
+ status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, error);
+ return true;
+ }
+ }
+ if (echo_stdout || echo_stderr) {
+ std::string command;
+ for (const auto& cmd : arguments.Commands) {
+ command += "'";
+ command += cmJoin(cmd, "' '");
+ command += "'";
+ command += "\n";
+ }
+ if (echo_stdout) {
+ std::cout << command;
+ } else if (echo_stderr) {
+ std::cerr << command;
+ }
+ }
+ // 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(
+ cmProcessOutput::FindEncoding(arguments.Encoding));
+ std::string strdata;
+ while ((p = cmsysProcess_WaitForData(cp, &data, &length, nullptr))) {
+ // Put the output in the right place.
+ if (p == cmsysProcess_Pipe_STDOUT && !arguments.OutputQuiet) {
+ if (arguments.OutputVariable.empty() || arguments.EchoOutputVariable) {
+ processOutput.DecodeText(data, length, strdata, 1);
+ cmSystemTools::Stdout(strdata);
+ }
+ if (!arguments.OutputVariable.empty()) {
+ cmExecuteProcessCommandAppend(tempOutput, data, length);
+ }
+ } else if (p == cmsysProcess_Pipe_STDERR && !arguments.ErrorQuiet) {
+ if (arguments.ErrorVariable.empty() || arguments.EchoErrorVariable) {
+ processOutput.DecodeText(data, length, strdata, 2);
+ cmSystemTools::Stderr(strdata);
+ }
+ if (!arguments.ErrorVariable.empty()) {
+ cmExecuteProcessCommandAppend(tempError, data, length);
+ }
+ }
+ }
+ if (!arguments.OutputQuiet &&
+ (arguments.OutputVariable.empty() || arguments.EchoOutputVariable)) {
+ processOutput.DecodeText(std::string(), strdata, 1);
+ if (!strdata.empty()) {
+ cmSystemTools::Stdout(strdata);
+ }
+ }
+ if (!arguments.ErrorQuiet &&
+ (arguments.ErrorVariable.empty() || arguments.EchoErrorVariable)) {
+ processOutput.DecodeText(std::string(), strdata, 2);
+ if (!strdata.empty()) {
+ cmSystemTools::Stderr(strdata);
+ }
+ }
+
+ // 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,
+ arguments.OutputStripTrailingWhitespace);
+ cmExecuteProcessCommandFixText(tempError,
+ arguments.ErrorStripTrailingWhitespace);
+
+ // Store the output obtained.
+ if (!arguments.OutputVariable.empty() && !tempOutput.empty()) {
+ status.GetMakefile().AddDefinition(arguments.OutputVariable,
+ tempOutput.data());
+ }
+ if (!merge_output && !arguments.ErrorVariable.empty() &&
+ !tempError.empty()) {
+ status.GetMakefile().AddDefinition(arguments.ErrorVariable,
+ tempError.data());
+ }
+
+ // Store the result of running the process.
+ if (!arguments.ResultVariable.empty()) {
+ switch (cmsysProcess_GetState(cp)) {
+ case cmsysProcess_State_Exited: {
+ int v = cmsysProcess_GetExitValue(cp);
+ char buf[16];
+ sprintf(buf, "%d", v);
+ status.GetMakefile().AddDefinition(arguments.ResultVariable, buf);
+ } break;
+ case cmsysProcess_State_Exception:
+ status.GetMakefile().AddDefinition(
+ arguments.ResultVariable, cmsysProcess_GetExceptionString(cp));
+ break;
+ case cmsysProcess_State_Error:
+ status.GetMakefile().AddDefinition(arguments.ResultVariable,
+ cmsysProcess_GetErrorString(cp));
+ break;
+ case cmsysProcess_State_Expired:
+ status.GetMakefile().AddDefinition(
+ arguments.ResultVariable, "Process terminated due to timeout");
+ break;
+ }
+ }
+ // Store the result of running the processes.
+ if (!arguments.ResultsVariable.empty()) {
+ switch (cmsysProcess_GetState(cp)) {
+ case cmsysProcess_State_Exited: {
+ std::vector<std::string> res;
+ for (size_t i = 0; i < arguments.Commands.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.emplace_back(buf);
+ } break;
+ case kwsysProcess_StateByIndex_Exception:
+ res.emplace_back(cmsysProcess_GetExceptionStringByIndex(
+ cp, static_cast<int>(i)));
+ break;
+ case kwsysProcess_StateByIndex_Error:
+ default:
+ res.emplace_back("Error getting the child return code");
+ break;
+ }
+ }
+ status.GetMakefile().AddDefinition(arguments.ResultsVariable,
+ cmJoin(res, ";"));
+ } break;
+ case cmsysProcess_State_Exception:
+ status.GetMakefile().AddDefinition(
+ arguments.ResultsVariable, cmsysProcess_GetExceptionString(cp));
+ break;
+ case cmsysProcess_State_Error:
+ status.GetMakefile().AddDefinition(arguments.ResultsVariable,
+ cmsysProcess_GetErrorString(cp));
+ break;
+ case cmsysProcess_State_Expired:
+ status.GetMakefile().AddDefinition(
+ arguments.ResultsVariable, "Process terminated due to timeout");
+ break;
+ }
+ }
+
+ auto queryProcessStatusByIndex = [&cp](int index) -> std::string {
+ std::string processStatus;
+ switch (cmsysProcess_GetStateByIndex(cp, static_cast<int>(index))) {
+ case kwsysProcess_StateByIndex_Exited: {
+ int exitCode = cmsysProcess_GetExitValueByIndex(cp, index);
+ if (exitCode) {
+ processStatus = "Child return code: " + std::to_string(exitCode);
+ }
+ } break;
+ case kwsysProcess_StateByIndex_Exception: {
+ processStatus = cmStrCat(
+ "Abnormal exit with child return code: ",
+ cmsysProcess_GetExceptionStringByIndex(cp, static_cast<int>(index)));
+ break;
+ }
+ case kwsysProcess_StateByIndex_Error:
+ default:
+ processStatus = "Error getting the child return code";
+ break;
+ }
+ return processStatus;
+ };
+
+ if (arguments.CommandErrorIsFatal == "ANY"_s) {
+ bool ret = true;
+ switch (cmsysProcess_GetState(cp)) {
+ case cmsysProcess_State_Exited: {
+ std::map<int, std::string> failureIndices;
+ for (int i = 0; i < static_cast<int>(arguments.Commands.size()); ++i) {
+ std::string processStatus = queryProcessStatusByIndex(i);
+ if (!processStatus.empty()) {
+ failureIndices[i] = processStatus;
+ }
+ if (!failureIndices.empty()) {
+ std::ostringstream oss;
+ oss << "failed command indexes:\n";
+ for (auto const& e : failureIndices) {
+ oss << " " << e.first + 1 << ": \"" << e.second << "\"\n";
+ }
+ status.SetError(oss.str());
+ ret = false;
+ }
+ }
+ } break;
+ case cmsysProcess_State_Exception:
+ status.SetError(
+ cmStrCat("abnormal exit: ", cmsysProcess_GetExceptionString(cp)));
+ ret = false;
+ break;
+ case cmsysProcess_State_Error:
+ status.SetError(cmStrCat("error getting child return code: ",
+ cmsysProcess_GetErrorString(cp)));
+ ret = false;
+ break;
+ case cmsysProcess_State_Expired:
+ status.SetError("Process terminated due to timeout");
+ ret = false;
+ break;
+ }
+
+ if (!ret) {
+ cmSystemTools::SetFatalErrorOccured();
+ return false;
+ }
+ }
+
+ if (arguments.CommandErrorIsFatal == "LAST"_s) {
+ bool ret = true;
+ switch (cmsysProcess_GetState(cp)) {
+ case cmsysProcess_State_Exited: {
+ int lastIndex = static_cast<int>(arguments.Commands.size() - 1);
+ const std::string processStatus = queryProcessStatusByIndex(lastIndex);
+ if (!processStatus.empty()) {
+ status.SetError("last command failed");
+ ret = false;
+ }
+ } break;
+ case cmsysProcess_State_Exception:
+ status.SetError(
+ cmStrCat("Abnormal exit: ", cmsysProcess_GetExceptionString(cp)));
+ ret = false;
+ break;
+ case cmsysProcess_State_Error:
+ status.SetError(cmStrCat("Error getting child return code: ",
+ cmsysProcess_GetErrorString(cp)));
+ ret = false;
+ break;
+ case cmsysProcess_State_Expired:
+ status.SetError("Process terminated due to timeout");
+ ret = false;
+ break;
+ }
+ if (!ret) {
+ cmSystemTools::SetFatalErrorOccured();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+namespace {
+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
+ cm::append(output, data, data + length);
+}
+}