222ea80a58ca8fd8244ef86a04d83bddd538e5d0
[platform/upstream/cmake.git] / Source / cmExecuteProcessCommand.cxx
1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 #include "cmExecuteProcessCommand.h"
4
5 #include <algorithm>
6 #include <cctype> /* isspace */
7 #include <cstdio>
8 #include <iostream>
9 #include <map>
10 #include <memory>
11 #include <sstream>
12 #include <utility>
13 #include <vector>
14
15 #include <cm/string_view>
16 #include <cmext/algorithm>
17 #include <cmext/string_view>
18
19 #include "cmsys/Process.h"
20
21 #include "cmArgumentParser.h"
22 #include "cmExecutionStatus.h"
23 #include "cmMakefile.h"
24 #include "cmMessageType.h"
25 #include "cmProcessOutput.h"
26 #include "cmStringAlgorithms.h"
27 #include "cmSystemTools.h"
28
29 namespace {
30 bool cmExecuteProcessCommandIsWhitespace(char c)
31 {
32   return (isspace(static_cast<int>(c)) || c == '\n' || c == '\r');
33 }
34
35 void cmExecuteProcessCommandFixText(std::vector<char>& output,
36                                     bool strip_trailing_whitespace);
37 void cmExecuteProcessCommandAppend(std::vector<char>& output, const char* data,
38                                    int length);
39 }
40
41 // cmExecuteProcessCommand
42 bool cmExecuteProcessCommand(std::vector<std::string> const& args,
43                              cmExecutionStatus& status)
44 {
45   if (args.empty()) {
46     status.SetError("called with incorrect number of arguments");
47     return false;
48   }
49
50   struct Arguments
51   {
52     std::vector<std::vector<std::string>> Commands;
53     std::string OutputVariable;
54     std::string ErrorVariable;
55     std::string ResultVariable;
56     std::string ResultsVariable;
57     std::string WorkingDirectory;
58     std::string InputFile;
59     std::string OutputFile;
60     std::string ErrorFile;
61     std::string Timeout;
62     std::string CommandEcho;
63     bool OutputQuiet = false;
64     bool ErrorQuiet = false;
65     bool OutputStripTrailingWhitespace = false;
66     bool ErrorStripTrailingWhitespace = false;
67     bool EchoOutputVariable = false;
68     bool EchoErrorVariable = false;
69     std::string Encoding;
70     std::string CommandErrorIsFatal;
71   };
72
73   static auto const parser =
74     cmArgumentParser<Arguments>{}
75       .Bind("COMMAND"_s, &Arguments::Commands)
76       .Bind("COMMAND_ECHO"_s, &Arguments::CommandEcho)
77       .Bind("OUTPUT_VARIABLE"_s, &Arguments::OutputVariable)
78       .Bind("ERROR_VARIABLE"_s, &Arguments::ErrorVariable)
79       .Bind("RESULT_VARIABLE"_s, &Arguments::ResultVariable)
80       .Bind("RESULTS_VARIABLE"_s, &Arguments::ResultsVariable)
81       .Bind("WORKING_DIRECTORY"_s, &Arguments::WorkingDirectory)
82       .Bind("INPUT_FILE"_s, &Arguments::InputFile)
83       .Bind("OUTPUT_FILE"_s, &Arguments::OutputFile)
84       .Bind("ERROR_FILE"_s, &Arguments::ErrorFile)
85       .Bind("TIMEOUT"_s, &Arguments::Timeout)
86       .Bind("OUTPUT_QUIET"_s, &Arguments::OutputQuiet)
87       .Bind("ERROR_QUIET"_s, &Arguments::ErrorQuiet)
88       .Bind("OUTPUT_STRIP_TRAILING_WHITESPACE"_s,
89             &Arguments::OutputStripTrailingWhitespace)
90       .Bind("ERROR_STRIP_TRAILING_WHITESPACE"_s,
91             &Arguments::ErrorStripTrailingWhitespace)
92       .Bind("ENCODING"_s, &Arguments::Encoding)
93       .Bind("ECHO_OUTPUT_VARIABLE"_s, &Arguments::EchoOutputVariable)
94       .Bind("ECHO_ERROR_VARIABLE"_s, &Arguments::EchoErrorVariable)
95       .Bind("COMMAND_ERROR_IS_FATAL"_s, &Arguments::CommandErrorIsFatal);
96
97   std::vector<std::string> unparsedArguments;
98   std::vector<std::string> keywordsMissingValue;
99   Arguments const arguments =
100     parser.Parse(args, &unparsedArguments, &keywordsMissingValue);
101
102   if (!keywordsMissingValue.empty()) {
103     status.SetError(" called with no value for " +
104                     keywordsMissingValue.front() + ".");
105     return false;
106   }
107   if (!unparsedArguments.empty()) {
108     status.SetError(" given unknown argument \"" + unparsedArguments.front() +
109                     "\".");
110     return false;
111   }
112
113   if (!status.GetMakefile().CanIWriteThisFile(arguments.OutputFile)) {
114     status.SetError("attempted to output into a file: " +
115                     arguments.OutputFile + " into a source directory.");
116     cmSystemTools::SetFatalErrorOccurred();
117     return false;
118   }
119
120   // Check for commands given.
121   if (arguments.Commands.empty()) {
122     status.SetError(" called with no COMMAND argument.");
123     return false;
124   }
125   for (std::vector<std::string> const& cmd : arguments.Commands) {
126     if (cmd.empty()) {
127       status.SetError(" given COMMAND argument with no value.");
128       return false;
129     }
130   }
131
132   // Parse the timeout string.
133   double timeout = -1;
134   if (!arguments.Timeout.empty()) {
135     if (sscanf(arguments.Timeout.c_str(), "%lg", &timeout) != 1) {
136       status.SetError(" called with TIMEOUT value that could not be parsed.");
137       return false;
138     }
139   }
140
141   if (!arguments.CommandErrorIsFatal.empty()) {
142     if (arguments.CommandErrorIsFatal != "ANY"_s &&
143         arguments.CommandErrorIsFatal != "LAST"_s) {
144       status.SetError("COMMAND_ERROR_IS_FATAL option can be ANY or LAST");
145       return false;
146     }
147   }
148   // Create a process instance.
149   std::unique_ptr<cmsysProcess, void (*)(cmsysProcess*)> cp_ptr(
150     cmsysProcess_New(), cmsysProcess_Delete);
151   cmsysProcess* cp = cp_ptr.get();
152
153   // Set the command sequence.
154   for (std::vector<std::string> const& cmd : arguments.Commands) {
155     std::vector<const char*> argv(cmd.size() + 1);
156     std::transform(cmd.begin(), cmd.end(), argv.begin(),
157                    [](std::string const& s) { return s.c_str(); });
158     argv.back() = nullptr;
159     cmsysProcess_AddCommand(cp, argv.data());
160   }
161
162   // Set the process working directory.
163   if (!arguments.WorkingDirectory.empty()) {
164     cmsysProcess_SetWorkingDirectory(cp, arguments.WorkingDirectory.c_str());
165   }
166
167   // Always hide the process window.
168   cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
169
170   // Check the output variables.
171   bool merge_output = false;
172   if (!arguments.InputFile.empty()) {
173     cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDIN,
174                              arguments.InputFile.c_str());
175   }
176   if (!arguments.OutputFile.empty()) {
177     cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDOUT,
178                              arguments.OutputFile.c_str());
179   }
180   if (!arguments.ErrorFile.empty()) {
181     if (arguments.ErrorFile == arguments.OutputFile) {
182       merge_output = true;
183     } else {
184       cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDERR,
185                                arguments.ErrorFile.c_str());
186     }
187   }
188   if (!arguments.OutputVariable.empty() &&
189       arguments.OutputVariable == arguments.ErrorVariable) {
190     merge_output = true;
191   }
192   if (merge_output) {
193     cmsysProcess_SetOption(cp, cmsysProcess_Option_MergeOutput, 1);
194   }
195
196   // Set the timeout if any.
197   if (timeout >= 0) {
198     cmsysProcess_SetTimeout(cp, timeout);
199   }
200
201   bool echo_stdout = false;
202   bool echo_stderr = false;
203   bool echo_output_from_variable = true;
204   std::string echo_output = status.GetMakefile().GetSafeDefinition(
205     "CMAKE_EXECUTE_PROCESS_COMMAND_ECHO");
206   if (!arguments.CommandEcho.empty()) {
207     echo_output_from_variable = false;
208     echo_output = arguments.CommandEcho;
209   }
210
211   if (!echo_output.empty()) {
212     if (echo_output == "STDERR") {
213       echo_stderr = true;
214     } else if (echo_output == "STDOUT") {
215       echo_stdout = true;
216     } else if (echo_output != "NONE") {
217       std::string error;
218       if (echo_output_from_variable) {
219         error = "CMAKE_EXECUTE_PROCESS_COMMAND_ECHO set to '";
220       } else {
221         error = " called with '";
222       }
223       error += echo_output;
224       error += "' expected STDERR|STDOUT|NONE";
225       if (!echo_output_from_variable) {
226         error += " for COMMAND_ECHO.";
227       }
228       status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, error);
229       return true;
230     }
231   }
232   if (echo_stdout || echo_stderr) {
233     std::string command;
234     for (const auto& cmd : arguments.Commands) {
235       command += "'";
236       command += cmJoin(cmd, "' '");
237       command += "'";
238       command += "\n";
239     }
240     if (echo_stdout) {
241       std::cout << command;
242     } else if (echo_stderr) {
243       std::cerr << command;
244     }
245   }
246   // Start the process.
247   cmsysProcess_Execute(cp);
248
249   // Read the process output.
250   std::vector<char> tempOutput;
251   std::vector<char> tempError;
252   int length;
253   char* data;
254   int p;
255   cmProcessOutput processOutput(
256     cmProcessOutput::FindEncoding(arguments.Encoding));
257   std::string strdata;
258   while ((p = cmsysProcess_WaitForData(cp, &data, &length, nullptr))) {
259     // Put the output in the right place.
260     if (p == cmsysProcess_Pipe_STDOUT && !arguments.OutputQuiet) {
261       if (arguments.OutputVariable.empty() || arguments.EchoOutputVariable) {
262         processOutput.DecodeText(data, length, strdata, 1);
263         cmSystemTools::Stdout(strdata);
264       }
265       if (!arguments.OutputVariable.empty()) {
266         cmExecuteProcessCommandAppend(tempOutput, data, length);
267       }
268     } else if (p == cmsysProcess_Pipe_STDERR && !arguments.ErrorQuiet) {
269       if (arguments.ErrorVariable.empty() || arguments.EchoErrorVariable) {
270         processOutput.DecodeText(data, length, strdata, 2);
271         cmSystemTools::Stderr(strdata);
272       }
273       if (!arguments.ErrorVariable.empty()) {
274         cmExecuteProcessCommandAppend(tempError, data, length);
275       }
276     }
277   }
278   if (!arguments.OutputQuiet &&
279       (arguments.OutputVariable.empty() || arguments.EchoOutputVariable)) {
280     processOutput.DecodeText(std::string(), strdata, 1);
281     if (!strdata.empty()) {
282       cmSystemTools::Stdout(strdata);
283     }
284   }
285   if (!arguments.ErrorQuiet &&
286       (arguments.ErrorVariable.empty() || arguments.EchoErrorVariable)) {
287     processOutput.DecodeText(std::string(), strdata, 2);
288     if (!strdata.empty()) {
289       cmSystemTools::Stderr(strdata);
290     }
291   }
292
293   // All output has been read.  Wait for the process to exit.
294   cmsysProcess_WaitForExit(cp, nullptr);
295   processOutput.DecodeText(tempOutput, tempOutput);
296   processOutput.DecodeText(tempError, tempError);
297
298   // Fix the text in the output strings.
299   cmExecuteProcessCommandFixText(tempOutput,
300                                  arguments.OutputStripTrailingWhitespace);
301   cmExecuteProcessCommandFixText(tempError,
302                                  arguments.ErrorStripTrailingWhitespace);
303
304   // Store the output obtained.
305   if (!arguments.OutputVariable.empty() && !tempOutput.empty()) {
306     status.GetMakefile().AddDefinition(arguments.OutputVariable,
307                                        tempOutput.data());
308   }
309   if (!merge_output && !arguments.ErrorVariable.empty() &&
310       !tempError.empty()) {
311     status.GetMakefile().AddDefinition(arguments.ErrorVariable,
312                                        tempError.data());
313   }
314
315   // Store the result of running the process.
316   if (!arguments.ResultVariable.empty()) {
317     switch (cmsysProcess_GetState(cp)) {
318       case cmsysProcess_State_Exited: {
319         int v = cmsysProcess_GetExitValue(cp);
320         char buf[16];
321         snprintf(buf, sizeof(buf), "%d", v);
322         status.GetMakefile().AddDefinition(arguments.ResultVariable, buf);
323       } break;
324       case cmsysProcess_State_Exception:
325         status.GetMakefile().AddDefinition(
326           arguments.ResultVariable, cmsysProcess_GetExceptionString(cp));
327         break;
328       case cmsysProcess_State_Error:
329         status.GetMakefile().AddDefinition(arguments.ResultVariable,
330                                            cmsysProcess_GetErrorString(cp));
331         break;
332       case cmsysProcess_State_Expired:
333         status.GetMakefile().AddDefinition(
334           arguments.ResultVariable, "Process terminated due to timeout");
335         break;
336     }
337   }
338   // Store the result of running the processes.
339   if (!arguments.ResultsVariable.empty()) {
340     switch (cmsysProcess_GetState(cp)) {
341       case cmsysProcess_State_Exited: {
342         std::vector<std::string> res;
343         for (size_t i = 0; i < arguments.Commands.size(); ++i) {
344           switch (cmsysProcess_GetStateByIndex(cp, static_cast<int>(i))) {
345             case kwsysProcess_StateByIndex_Exited: {
346               int exitCode =
347                 cmsysProcess_GetExitValueByIndex(cp, static_cast<int>(i));
348               char buf[16];
349               snprintf(buf, sizeof(buf), "%d", exitCode);
350               res.emplace_back(buf);
351             } break;
352             case kwsysProcess_StateByIndex_Exception:
353               res.emplace_back(cmsysProcess_GetExceptionStringByIndex(
354                 cp, static_cast<int>(i)));
355               break;
356             case kwsysProcess_StateByIndex_Error:
357             default:
358               res.emplace_back("Error getting the child return code");
359               break;
360           }
361         }
362         status.GetMakefile().AddDefinition(arguments.ResultsVariable,
363                                            cmJoin(res, ";"));
364       } break;
365       case cmsysProcess_State_Exception:
366         status.GetMakefile().AddDefinition(
367           arguments.ResultsVariable, cmsysProcess_GetExceptionString(cp));
368         break;
369       case cmsysProcess_State_Error:
370         status.GetMakefile().AddDefinition(arguments.ResultsVariable,
371                                            cmsysProcess_GetErrorString(cp));
372         break;
373       case cmsysProcess_State_Expired:
374         status.GetMakefile().AddDefinition(
375           arguments.ResultsVariable, "Process terminated due to timeout");
376         break;
377     }
378   }
379
380   auto queryProcessStatusByIndex = [&cp](int index) -> std::string {
381     std::string processStatus;
382     switch (cmsysProcess_GetStateByIndex(cp, static_cast<int>(index))) {
383       case kwsysProcess_StateByIndex_Exited: {
384         int exitCode = cmsysProcess_GetExitValueByIndex(cp, index);
385         if (exitCode) {
386           processStatus = "Child return code: " + std::to_string(exitCode);
387         }
388       } break;
389       case kwsysProcess_StateByIndex_Exception: {
390         processStatus = cmStrCat(
391           "Abnormal exit with child return code: ",
392           cmsysProcess_GetExceptionStringByIndex(cp, static_cast<int>(index)));
393         break;
394       }
395       case kwsysProcess_StateByIndex_Error:
396       default:
397         processStatus = "Error getting the child return code";
398         break;
399     }
400     return processStatus;
401   };
402
403   if (arguments.CommandErrorIsFatal == "ANY"_s) {
404     bool ret = true;
405     switch (cmsysProcess_GetState(cp)) {
406       case cmsysProcess_State_Exited: {
407         std::map<int, std::string> failureIndices;
408         for (int i = 0; i < static_cast<int>(arguments.Commands.size()); ++i) {
409           std::string processStatus = queryProcessStatusByIndex(i);
410           if (!processStatus.empty()) {
411             failureIndices[i] = processStatus;
412           }
413           if (!failureIndices.empty()) {
414             std::ostringstream oss;
415             oss << "failed command indexes:\n";
416             for (auto const& e : failureIndices) {
417               oss << "  " << e.first + 1 << ": \"" << e.second << "\"\n";
418             }
419             status.SetError(oss.str());
420             ret = false;
421           }
422         }
423       } break;
424       case cmsysProcess_State_Exception:
425         status.SetError(
426           cmStrCat("abnormal exit: ", cmsysProcess_GetExceptionString(cp)));
427         ret = false;
428         break;
429       case cmsysProcess_State_Error:
430         status.SetError(cmStrCat("error getting child return code: ",
431                                  cmsysProcess_GetErrorString(cp)));
432         ret = false;
433         break;
434       case cmsysProcess_State_Expired:
435         status.SetError("Process terminated due to timeout");
436         ret = false;
437         break;
438     }
439
440     if (!ret) {
441       cmSystemTools::SetFatalErrorOccurred();
442       return false;
443     }
444   }
445
446   if (arguments.CommandErrorIsFatal == "LAST"_s) {
447     bool ret = true;
448     switch (cmsysProcess_GetState(cp)) {
449       case cmsysProcess_State_Exited: {
450         int lastIndex = static_cast<int>(arguments.Commands.size() - 1);
451         const std::string processStatus = queryProcessStatusByIndex(lastIndex);
452         if (!processStatus.empty()) {
453           status.SetError("last command failed");
454           ret = false;
455         }
456       } break;
457       case cmsysProcess_State_Exception:
458         status.SetError(
459           cmStrCat("Abnormal exit: ", cmsysProcess_GetExceptionString(cp)));
460         ret = false;
461         break;
462       case cmsysProcess_State_Error:
463         status.SetError(cmStrCat("Error getting child return code: ",
464                                  cmsysProcess_GetErrorString(cp)));
465         ret = false;
466         break;
467       case cmsysProcess_State_Expired:
468         status.SetError("Process terminated due to timeout");
469         ret = false;
470         break;
471     }
472     if (!ret) {
473       cmSystemTools::SetFatalErrorOccurred();
474       return false;
475     }
476   }
477
478   return true;
479 }
480
481 namespace {
482 void cmExecuteProcessCommandFixText(std::vector<char>& output,
483                                     bool strip_trailing_whitespace)
484 {
485   // Remove \0 characters and the \r part of \r\n pairs.
486   unsigned int in_index = 0;
487   unsigned int out_index = 0;
488   while (in_index < output.size()) {
489     char c = output[in_index++];
490     if ((c != '\r' ||
491          !(in_index < output.size() && output[in_index] == '\n')) &&
492         c != '\0') {
493       output[out_index++] = c;
494     }
495   }
496
497   // Remove trailing whitespace if requested.
498   if (strip_trailing_whitespace) {
499     while (out_index > 0 &&
500            cmExecuteProcessCommandIsWhitespace(output[out_index - 1])) {
501       --out_index;
502     }
503   }
504
505   // Shrink the vector to the size needed.
506   output.resize(out_index);
507
508   // Put a terminator on the text string.
509   output.push_back('\0');
510 }
511
512 void cmExecuteProcessCommandAppend(std::vector<char>& output, const char* data,
513                                    int length)
514 {
515 #if defined(__APPLE__)
516   // HACK on Apple to work around bug with inserting at the
517   // end of an empty vector.  This resulted in random failures
518   // that were hard to reproduce.
519   if (output.empty() && length > 0) {
520     output.push_back(data[0]);
521     ++data;
522     --length;
523   }
524 #endif
525   cm::append(output, data, data + length);
526 }
527 }