Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmExecProgramCommand.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmExecProgramCommand.h"
4
5
#include <cstdio>
6
#include <utility>
7
8
#include "cmsys/Process.h"
9
10
#include "cmExecutionStatus.h"
11
#include "cmMakefile.h"
12
#include "cmProcessOutput.h"
13
#include "cmStringAlgorithms.h"
14
#include "cmSystemTools.h"
15
#include "cmake.h"
16
17
using Encoding = cmProcessOutput::Encoding;
18
19
namespace {
20
bool RunCommand(std::string command, std::string& output, int& retVal,
21
                char const* directory = nullptr, bool verbose = true,
22
                Encoding encoding = cmProcessOutput::Auto);
23
}
24
25
// cmExecProgramCommand
26
bool cmExecProgramCommand(std::vector<std::string> const& args,
27
                          cmExecutionStatus& status)
28
0
{
29
0
  if (args.empty()) {
30
0
    status.SetError("called with incorrect number of arguments");
31
0
    return false;
32
0
  }
33
0
  std::string arguments;
34
0
  bool doingargs = false;
35
0
  int count = 0;
36
0
  std::string output_variable;
37
0
  bool haveoutput_variable = false;
38
0
  std::string return_variable;
39
0
  bool havereturn_variable = false;
40
0
  for (std::string const& arg : args) {
41
0
    if (arg == "OUTPUT_VARIABLE") {
42
0
      count++;
43
0
      doingargs = false;
44
0
      havereturn_variable = false;
45
0
      haveoutput_variable = true;
46
0
    } else if (haveoutput_variable) {
47
0
      if (!output_variable.empty()) {
48
0
        status.SetError("called with incorrect number of arguments");
49
0
        return false;
50
0
      }
51
0
      output_variable = arg;
52
0
      haveoutput_variable = false;
53
0
      count++;
54
0
    } else if (arg == "RETURN_VALUE") {
55
0
      count++;
56
0
      doingargs = false;
57
0
      haveoutput_variable = false;
58
0
      havereturn_variable = true;
59
0
    } else if (havereturn_variable) {
60
0
      if (!return_variable.empty()) {
61
0
        status.SetError("called with incorrect number of arguments");
62
0
        return false;
63
0
      }
64
0
      return_variable = arg;
65
0
      havereturn_variable = false;
66
0
      count++;
67
0
    } else if (arg == "ARGS") {
68
0
      count++;
69
0
      havereturn_variable = false;
70
0
      haveoutput_variable = false;
71
0
      doingargs = true;
72
0
    } else if (doingargs) {
73
0
      arguments = cmStrCat(std::move(arguments), arg, ' ');
74
0
      count++;
75
0
    }
76
0
  }
77
78
0
  std::string command;
79
0
  if (!arguments.empty()) {
80
0
    command = cmStrCat(cmSystemTools::ConvertToRunCommandPath(args[0]), ' ',
81
0
                       arguments);
82
0
  } else {
83
0
    command = args[0];
84
0
  }
85
0
  bool verbose = true;
86
0
  if (!output_variable.empty()) {
87
0
    verbose = false;
88
0
  }
89
0
  int retVal = 0;
90
0
  std::string output;
91
0
  bool result = true;
92
0
  if (args.size() - count == 2) {
93
0
    cmSystemTools::MakeDirectory(args[1]);
94
0
    result = RunCommand(command, output, retVal, args[1].c_str(), verbose);
95
0
  } else {
96
0
    result = RunCommand(command, output, retVal, nullptr, verbose);
97
0
  }
98
0
  if (!result) {
99
0
    retVal = -1;
100
0
  }
101
102
  // The process may have removed or renamed files.
103
0
  status.GetMakefile().GetCMakeInstance()->InvalidateFileSystemCaches();
104
105
0
  if (!output_variable.empty()) {
106
0
    std::string::size_type first = output.find_first_not_of(" \n\t\r");
107
0
    std::string::size_type last = output.find_last_not_of(" \n\t\r");
108
0
    if (first == std::string::npos) {
109
0
      first = 0;
110
0
    }
111
0
    if (last == std::string::npos) {
112
0
      last = output.size() - 1;
113
0
    }
114
115
0
    std::string coutput = std::string(output, first, last - first + 1);
116
0
    status.GetMakefile().AddDefinition(output_variable, coutput);
117
0
  }
118
119
0
  if (!return_variable.empty()) {
120
0
    char buffer[100];
121
0
    snprintf(buffer, sizeof(buffer), "%d", retVal);
122
0
    status.GetMakefile().AddDefinition(return_variable, buffer);
123
0
  }
124
125
0
  return true;
126
0
}
127
128
namespace {
129
bool RunCommand(std::string command, std::string& output, int& retVal,
130
                char const* dir, bool verbose, Encoding encoding)
131
0
{
132
0
  if (cmSystemTools::GetRunCommandOutput()) {
133
0
    verbose = false;
134
0
  }
135
136
#if defined(_WIN32) && !defined(__CYGWIN__)
137
  // if the command does not start with a quote, then
138
  // try to find the program, and if the program can not be
139
  // found use system to run the command as it must be a built in
140
  // shell command like echo or dir
141
  if (!command.empty() && command[0] == '\"') {
142
    // count the number of quotes
143
    int count = 0;
144
    for (char c : command) {
145
      if (c == '\"') {
146
        count++;
147
        if (count > 2) {
148
          break;
149
        }
150
      }
151
    }
152
    // if there are more than two double quotes use
153
    // GetShortPathName, the cmd.exe program in windows which
154
    // is used by system fails to execute if there are more than
155
    // one set of quotes in the arguments
156
    if (count > 2) {
157
      cmsys::RegularExpression quoted("^\"([^\"]*)\"[ \t](.*)");
158
      if (quoted.find(command)) {
159
        std::string shortCmd;
160
        std::string cmd = quoted.match(1);
161
        std::string args = quoted.match(2);
162
        if (!cmSystemTools::FileExists(cmd)) {
163
          shortCmd = std::move(cmd);
164
        } else if (!cmSystemTools::GetShortPath(cmd, shortCmd)) {
165
          cmSystemTools::Error("GetShortPath failed for " + cmd);
166
          return false;
167
        }
168
        shortCmd = cmStrCat(std::move(shortCmd), ' ', std::move(args));
169
170
        command = shortCmd;
171
      } else {
172
        cmSystemTools::Error("Could not parse command line with quotes " +
173
                             command);
174
      }
175
    }
176
  }
177
#endif
178
179
  // Allocate a process instance.
180
0
  cmsysProcess* cp = cmsysProcess_New();
181
0
  if (!cp) {
182
0
    cmSystemTools::Error("Error allocating process instance.");
183
0
    return false;
184
0
  }
185
186
#if defined(_WIN32) && !defined(__CYGWIN__)
187
  if (dir) {
188
    cmsysProcess_SetWorkingDirectory(cp, dir);
189
  }
190
  if (cmSystemTools::GetRunCommandHideConsole()) {
191
    cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
192
  }
193
  cmsysProcess_SetOption(cp, cmsysProcess_Option_Verbatim, 1);
194
  char const* cmd[] = { command.c_str(), nullptr };
195
  cmsysProcess_SetCommand(cp, cmd);
196
#else
197
0
  std::string commandInDir;
198
0
  if (dir) {
199
0
    commandInDir = cmStrCat("cd \"", dir, "\" && ", command);
200
0
  } else {
201
0
    commandInDir = command;
202
0
  }
203
0
#  ifndef __VMS
204
0
  commandInDir += " 2>&1";
205
0
#  endif
206
0
  command = commandInDir;
207
0
  if (verbose) {
208
0
    cmSystemTools::Stdout(cmStrCat("running ", command, '\n'));
209
0
  }
210
0
  fflush(stdout);
211
0
  fflush(stderr);
212
0
  char const* cmd[] = { "/bin/sh", "-c", command.c_str(), nullptr };
213
0
  cmsysProcess_SetCommand(cp, cmd);
214
0
#endif
215
216
0
  cmsysProcess_Execute(cp);
217
218
  // Read the process output.
219
0
  int length;
220
0
  char* data;
221
0
  int p;
222
0
  cmProcessOutput processOutput(encoding);
223
0
  std::string strdata;
224
0
  while ((p = cmsysProcess_WaitForData(cp, &data, &length, nullptr))) {
225
0
    if (p == cmsysProcess_Pipe_STDOUT || p == cmsysProcess_Pipe_STDERR) {
226
0
      if (verbose) {
227
0
        processOutput.DecodeText(data, length, strdata);
228
0
        cmSystemTools::Stdout(strdata);
229
0
      }
230
0
      output.append(data, length);
231
0
    }
232
0
  }
233
234
0
  if (verbose) {
235
0
    processOutput.DecodeText(std::string(), strdata);
236
0
    if (!strdata.empty()) {
237
0
      cmSystemTools::Stdout(strdata);
238
0
    }
239
0
  }
240
241
  // All output has been read.  Wait for the process to exit.
242
0
  cmsysProcess_WaitForExit(cp, nullptr);
243
0
  processOutput.DecodeText(output, output);
244
245
  // Check the result of running the process.
246
0
  std::string msg;
247
0
  switch (cmsysProcess_GetState(cp)) {
248
0
    case cmsysProcess_State_Exited:
249
0
      retVal = cmsysProcess_GetExitValue(cp);
250
0
      break;
251
0
    case cmsysProcess_State_Exception:
252
0
      retVal = -1;
253
0
      msg = cmStrCat(std::move(msg), "\nProcess terminated due to: ",
254
0
                     cmsysProcess_GetExceptionString(cp));
255
0
      break;
256
0
    case cmsysProcess_State_Error:
257
0
      retVal = -1;
258
0
      msg = cmStrCat(std::move(msg), "\nProcess failed because: ",
259
0
                     cmsysProcess_GetErrorString(cp));
260
0
      break;
261
0
    case cmsysProcess_State_Expired:
262
0
      retVal = -1;
263
0
      msg += "\nProcess terminated due to timeout.";
264
0
      break;
265
0
  }
266
0
  if (!msg.empty()) {
267
#if defined(_WIN32) && !defined(__CYGWIN__)
268
    // Old Windows process execution printed this info.
269
    msg = cmStrCat(std::move(msg), "\n\nfor command: ", command);
270
    if (dir) {
271
      msg = cmStrCat(std::move(msg), "\nin dir: ", dir);
272
    }
273
    msg += "\n";
274
    if (verbose) {
275
      cmSystemTools::Stdout(msg);
276
    }
277
    output += msg;
278
#else
279
    // Old UNIX process execution only put message in output.
280
0
    output += msg;
281
0
#endif
282
0
  }
283
284
  // Delete the process instance.
285
0
  cmsysProcess_Delete(cp);
286
287
0
  return true;
288
0
}
289
}