Coverage Report

Created: 2026-02-09 06:05

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