Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmOutputConverter.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 "cmOutputConverter.h"
4
5
#include <algorithm>
6
#include <cassert>
7
#include <set>
8
#include <vector>
9
10
#ifdef _WIN32
11
#  include <unordered_map>
12
#  include <utility>
13
#endif
14
15
#include "cmsys/String.h"
16
17
#include "cmList.h"
18
#include "cmState.h"
19
#include "cmStateDirectory.h"
20
#include "cmSystemTools.h"
21
#include "cmValue.h"
22
23
namespace {
24
bool PathEqOrSubDir(std::string const& a, std::string const& b)
25
0
{
26
0
  return (cmSystemTools::ComparePath(a, b) ||
27
0
          cmSystemTools::IsSubDirectory(a, b));
28
0
}
29
}
30
31
cmOutputConverter::cmOutputConverter(cmStateSnapshot const& snapshot)
32
5.98k
  : StateSnapshot(snapshot)
33
5.98k
{
34
5.98k
  assert(this->StateSnapshot.IsValid());
35
5.98k
  this->ComputeRelativePathTopSource();
36
5.98k
  this->ComputeRelativePathTopBinary();
37
5.98k
  this->ComputeRelativePathTopRelation();
38
5.98k
}
39
40
void cmOutputConverter::ComputeRelativePathTopSource()
41
5.98k
{
42
  // Walk up the buildsystem directory tree to find the highest source
43
  // directory that contains the current source directory.
44
5.98k
  cmStateSnapshot snapshot = this->StateSnapshot;
45
5.98k
  for (cmStateSnapshot parent = snapshot.GetBuildsystemDirectoryParent();
46
5.98k
       parent.IsValid(); parent = parent.GetBuildsystemDirectoryParent()) {
47
0
    if (cmSystemTools::IsSubDirectory(
48
0
          snapshot.GetDirectory().GetCurrentSource(),
49
0
          parent.GetDirectory().GetCurrentSource())) {
50
0
      snapshot = parent;
51
0
    }
52
0
  }
53
5.98k
  this->RelativePathTopSource = snapshot.GetDirectory().GetCurrentSource();
54
5.98k
}
55
56
void cmOutputConverter::ComputeRelativePathTopBinary()
57
5.98k
{
58
  // Walk up the buildsystem directory tree to find the highest binary
59
  // directory that contains the current binary directory.
60
5.98k
  cmStateSnapshot snapshot = this->StateSnapshot;
61
5.98k
  for (cmStateSnapshot parent = snapshot.GetBuildsystemDirectoryParent();
62
5.98k
       parent.IsValid(); parent = parent.GetBuildsystemDirectoryParent()) {
63
0
    if (cmSystemTools::IsSubDirectory(
64
0
          snapshot.GetDirectory().GetCurrentBinary(),
65
0
          parent.GetDirectory().GetCurrentBinary())) {
66
0
      snapshot = parent;
67
0
    }
68
0
  }
69
70
5.98k
  this->RelativePathTopBinary = snapshot.GetDirectory().GetCurrentBinary();
71
5.98k
}
72
73
void cmOutputConverter::ComputeRelativePathTopRelation()
74
5.98k
{
75
5.98k
  if (cmSystemTools::ComparePath(this->RelativePathTopSource,
76
5.98k
                                 this->RelativePathTopBinary)) {
77
0
    this->RelativePathTopRelation = TopRelation::InSource;
78
5.98k
  } else if (cmSystemTools::IsSubDirectory(this->RelativePathTopBinary,
79
5.98k
                                           this->RelativePathTopSource)) {
80
5.98k
    this->RelativePathTopRelation = TopRelation::BinInSrc;
81
5.98k
  } else if (cmSystemTools::IsSubDirectory(this->RelativePathTopSource,
82
0
                                           this->RelativePathTopBinary)) {
83
0
    this->RelativePathTopRelation = TopRelation::SrcInBin;
84
0
  } else {
85
0
    this->RelativePathTopRelation = TopRelation::Separate;
86
0
  }
87
5.98k
}
88
89
std::string const& cmOutputConverter::GetRelativePathTopSource() const
90
0
{
91
0
  return this->RelativePathTopSource;
92
0
}
93
94
std::string const& cmOutputConverter::GetRelativePathTopBinary() const
95
0
{
96
0
  return this->RelativePathTopBinary;
97
0
}
98
99
void cmOutputConverter::SetRelativePathTop(std::string const& topSource,
100
                                           std::string const& topBinary)
101
0
{
102
0
  this->RelativePathTopSource = topSource;
103
0
  this->RelativePathTopBinary = topBinary;
104
0
  this->ComputeRelativePathTopRelation();
105
0
}
106
107
std::string cmOutputConverter::MaybeRelativeTo(
108
  std::string const& local_path, std::string const& remote_path) const
109
0
{
110
0
  bool localInBinary = PathEqOrSubDir(local_path, this->RelativePathTopBinary);
111
0
  bool remoteInBinary =
112
0
    PathEqOrSubDir(remote_path, this->RelativePathTopBinary);
113
114
0
  bool localInSource = PathEqOrSubDir(local_path, this->RelativePathTopSource);
115
0
  bool remoteInSource =
116
0
    PathEqOrSubDir(remote_path, this->RelativePathTopSource);
117
118
0
  switch (this->RelativePathTopRelation) {
119
0
    case TopRelation::Separate:
120
      // Checks are independent.
121
0
      break;
122
0
    case TopRelation::BinInSrc:
123
0
      localInSource = localInSource && !localInBinary;
124
0
      remoteInSource = remoteInSource && !remoteInBinary;
125
0
      break;
126
0
    case TopRelation::SrcInBin:
127
0
      localInBinary = localInBinary && !localInSource;
128
0
      remoteInBinary = remoteInBinary && !remoteInSource;
129
0
      break;
130
0
    case TopRelation::InSource:
131
      // Checks are identical.
132
0
      break;
133
0
  };
134
135
0
  bool const bothInBinary = localInBinary && remoteInBinary;
136
0
  bool const bothInSource = localInSource && remoteInSource;
137
138
0
  if (bothInBinary || bothInSource) {
139
0
    return cmSystemTools::ForceToRelativePath(local_path, remote_path);
140
0
  }
141
0
  return remote_path;
142
0
}
143
144
std::string cmOutputConverter::MaybeRelativeToTopBinDir(
145
  std::string const& path) const
146
0
{
147
0
  return this->MaybeRelativeTo(this->GetState()->GetBinaryDirectory(), path);
148
0
}
149
150
std::string cmOutputConverter::MaybeRelativeToCurBinDir(
151
  std::string const& path) const
152
0
{
153
0
  return this->MaybeRelativeTo(
154
0
    this->StateSnapshot.GetDirectory().GetCurrentBinary(), path);
155
0
}
156
157
std::string cmOutputConverter::ConvertToOutputForExisting(
158
  cm::string_view path, OutputFormat format, bool useWatcomQuote) const
159
0
{
160
#ifdef _WIN32
161
  // Cache the short paths since `GetShortPathNameW` is really expensive.
162
  static std::unordered_map<std::string, std::string> shortPathCache{};
163
164
  // If this is a windows shell, the path has a space, and the path already
165
  // exists, we can use a short-path to reference it without a space.
166
  if (this->GetState()->UseWindowsShell() &&
167
      path.find_first_of(" #") != std::string::npos) {
168
    auto origPath = std::string(path);
169
    if (cmSystemTools::FileExists(origPath)) {
170
      auto i = shortPathCache.find(origPath);
171
      if (i == shortPathCache.end()) {
172
        std::string shortPath;
173
        if (!cmSystemTools::GetShortPath(origPath, shortPath)) {
174
          // Fallback for cases when Windows refuses to resolve
175
          // the short path, like for `C:\Program Files\WindowsApps\...`.
176
          shortPath = origPath;
177
        }
178
        i = shortPathCache.emplace(std::move(origPath), std::move(shortPath))
179
              .first;
180
      }
181
      path = i->second;
182
    }
183
  }
184
#endif
185
186
  // Otherwise, perform standard conversion.
187
0
  return this->ConvertToOutputFormat(path, format, useWatcomQuote);
188
0
}
189
190
std::string cmOutputConverter::ConvertToOutputFormat(cm::string_view source,
191
                                                     OutputFormat format,
192
                                                     bool useWatcomQuote) const
193
0
{
194
0
  std::string result;
195
  // Convert it to an output path.
196
0
  if (format == SHELL || format == NINJAMULTI) {
197
0
    result = this->ConvertDirectorySeparatorsForShell(source);
198
0
    result = this->EscapeForShell(result, true, false, useWatcomQuote,
199
0
                                  format == NINJAMULTI);
200
0
  } else if (format == RESPONSE) {
201
0
    result =
202
0
      this->EscapeForShell(source, false, false, useWatcomQuote, false, true);
203
0
  } else {
204
0
    result = std::string(source);
205
0
  }
206
0
  return result;
207
0
}
208
209
std::string cmOutputConverter::ConvertDirectorySeparatorsForShell(
210
  cm::string_view source) const
211
219
{
212
219
  std::string result(source);
213
  // For the MSYS shell convert drive letters to posix paths, so
214
  // that c:/some/path becomes /c/some/path.  This is needed to
215
  // avoid problems with the shell path translation.
216
219
  if (this->GetState()->UseMSYSShell() && !this->LinkScriptShell) {
217
0
    if (result.size() > 2 && result[1] == ':') {
218
0
      result[1] = result[0];
219
0
      result[0] = '/';
220
0
    }
221
0
  }
222
219
  if (this->GetState()->UseWindowsShell()) {
223
0
    std::replace(result.begin(), result.end(), '/', '\\');
224
0
  }
225
219
  return result;
226
219
}
227
228
static bool cmOutputConverterIsShellOperator(cm::string_view str)
229
41.3k
{
230
41.3k
  static std::set<cm::string_view> const shellOperators{
231
41.3k
    "<", ">", "<<", ">>", "|", "||", "&&", "&>", "1>", "2>", "2>&1", "1>&2"
232
41.3k
  };
233
41.3k
  return (shellOperators.count(str) != 0);
234
41.3k
}
235
236
std::string cmOutputConverter::EscapeForShell(cm::string_view str,
237
                                              bool makeVars, bool forEcho,
238
                                              bool useWatcomQuote,
239
                                              bool unescapeNinjaConfiguration,
240
                                              bool forResponse) const
241
0
{
242
  // Compute the flags for the target shell environment.
243
0
  int flags = 0;
244
0
  if (this->GetState()->UseWindowsVSIDE()) {
245
0
    flags |= Shell_Flag_VSIDE;
246
0
  } else if (!this->LinkScriptShell) {
247
0
    flags |= Shell_Flag_Make;
248
0
  }
249
0
  if (unescapeNinjaConfiguration) {
250
0
    flags |= Shell_Flag_UnescapeNinjaConfiguration;
251
0
  }
252
0
  if (makeVars) {
253
0
    flags |= Shell_Flag_AllowMakeVariables;
254
0
  }
255
0
  if (forEcho) {
256
0
    flags |= Shell_Flag_EchoWindows;
257
0
  }
258
0
  if (useWatcomQuote) {
259
0
    flags |= Shell_Flag_WatcomQuote;
260
0
  }
261
0
  if (forResponse) {
262
0
    flags |= Shell_Flag_IsResponse;
263
0
  }
264
0
  if (this->GetState()->UseWatcomWMake()) {
265
0
    flags |= Shell_Flag_WatcomWMake;
266
0
  }
267
0
  if (this->GetState()->UseMinGWMake()) {
268
0
    flags |= Shell_Flag_MinGWMake;
269
0
  }
270
0
  if (this->GetState()->UseNMake()) {
271
0
    flags |= Shell_Flag_NMake;
272
0
  }
273
0
  if (this->GetState()->UseNinja()) {
274
0
    flags |= Shell_Flag_Ninja;
275
0
  }
276
0
  if (!this->GetState()->UseWindowsShell()) {
277
0
    flags |= Shell_Flag_IsUnix;
278
0
  }
279
0
  if (this->GetState()->UseFastbuildMake()) {
280
    // Fastbuild needs to escape very few characters.
281
0
    flags = Shell_Flag_Fastbuild;
282
0
  }
283
284
0
  return cmOutputConverter::EscapeForShell(str, flags);
285
0
}
286
287
std::string cmOutputConverter::EscapeForShell(cm::string_view str, int flags)
288
41.3k
{
289
  // Do not escape shell operators.
290
41.3k
  if (cmOutputConverterIsShellOperator(str)) {
291
0
    return std::string(str);
292
0
  }
293
294
41.3k
  return Shell_GetArgument(str, flags);
295
41.3k
}
296
297
std::string cmOutputConverter::EscapeForCMake(cm::string_view str,
298
                                              WrapQuotes wrapQuotes)
299
0
{
300
  // Always double-quote the argument to take care of most escapes.
301
0
  std::string result = (wrapQuotes == WrapQuotes::Wrap) ? "\"" : "";
302
0
  for (char const c : str) {
303
0
    if (c == '"') {
304
      // Escape the double quote to avoid ending the argument.
305
0
      result += "\\\"";
306
0
    } else if (c == '$') {
307
      // Escape the dollar to avoid expanding variables.
308
0
      result += "\\$";
309
0
    } else if (c == '\\') {
310
      // Escape the backslash to avoid other escapes.
311
0
      result += "\\\\";
312
0
    } else {
313
      // Other characters will be parsed correctly.
314
0
      result += c;
315
0
    }
316
0
  }
317
0
  if (wrapQuotes == WrapQuotes::Wrap) {
318
0
    result += "\"";
319
0
  }
320
0
  return result;
321
0
}
322
323
std::string cmOutputConverter::EscapeWindowsShellArgument(cm::string_view arg,
324
                                                          int shell_flags)
325
0
{
326
0
  return Shell_GetArgument(arg, shell_flags);
327
0
}
328
329
cmOutputConverter::FortranFormat cmOutputConverter::GetFortranFormat(
330
  cm::string_view value)
331
0
{
332
0
  FortranFormat format = FortranFormatNone;
333
0
  if (!value.empty()) {
334
0
    for (std::string const& fi : cmList(value)) {
335
0
      if (fi == "FIXED") {
336
0
        format = FortranFormatFixed;
337
0
      }
338
0
      if (fi == "FREE") {
339
0
        format = FortranFormatFree;
340
0
      }
341
0
    }
342
0
  }
343
0
  return format;
344
0
}
345
346
cmOutputConverter::FortranPreprocess cmOutputConverter::GetFortranPreprocess(
347
  cm::string_view value)
348
0
{
349
0
  if (value.empty()) {
350
0
    return FortranPreprocess::Unset;
351
0
  }
352
353
0
  return cmIsOn(value) ? FortranPreprocess::Needed
354
0
                       : FortranPreprocess::NotNeeded;
355
0
}
356
357
void cmOutputConverter::SetLinkScriptShell(bool linkScriptShell)
358
0
{
359
0
  this->LinkScriptShell = linkScriptShell;
360
0
}
361
362
cmState* cmOutputConverter::GetState() const
363
438
{
364
438
  return this->StateSnapshot.GetState();
365
438
}
366
367
/*
368
369
Notes:
370
371
Make variable replacements open a can of worms.  Sometimes they should
372
be quoted and sometimes not.  Sometimes their replacement values are
373
already quoted.
374
375
VS variables cause problems.  In order to pass the referenced value
376
with spaces the reference must be quoted.  If the variable value ends
377
in a backslash then it will escape the ending quote!  In order to make
378
the ending backslash appear we need this:
379
380
  "$(InputDir)\"
381
382
However if there is not a trailing backslash then this will put a
383
quote in the value so we need:
384
385
  "$(InputDir)"
386
387
Make variable references are platform specific so we should probably
388
just NOT quote them and let the listfile author deal with it.
389
390
*/
391
392
/*
393
TODO: For windows echo:
394
395
To display a pipe (|) or redirection character (< or >) when using the
396
echo command, use a caret character immediately before the pipe or
397
redirection character (for example, ^>, ^<, or ^| ). If you need to
398
use the caret character itself (^), use two in a row (^^).
399
*/
400
401
/* Some helpers to identify character classes */
402
static bool Shell_CharIsWhitespace(char c)
403
537k
{
404
537k
  return ((c == ' ') || (c == '\t'));
405
537k
}
406
407
static bool Shell_CharNeedsQuotesOnUnix(char c)
408
537k
{
409
537k
  return ((c == '\'') || (c == '`') || (c == ';') || (c == '#') ||
410
537k
          (c == '&') || (c == '$') || (c == '(') || (c == ')') || (c == '~') ||
411
537k
          (c == '<') || (c == '>') || (c == '|') || (c == '*') || (c == '^') ||
412
537k
          (c == '\\'));
413
537k
}
414
415
static bool Shell_CharNeedsQuotesOnWindows(char c)
416
0
{
417
0
  return ((c == '\'') || (c == '#') || (c == '&') || (c == '<') ||
418
0
          (c == '>') || (c == '|') || (c == '^'));
419
0
}
420
421
static bool Shell_CharIsMakeVariableName(char c)
422
0
{
423
0
  return c && (c == '_' || cmsysString_isalpha((static_cast<int>(c))));
424
0
}
425
426
bool cmOutputConverter::Shell_CharNeedsQuotes(char c, int flags)
427
537k
{
428
  /* On Windows the built-in command shell echo never needs quotes.  */
429
537k
  if (!(flags & Shell_Flag_IsUnix) && (flags & Shell_Flag_EchoWindows)) {
430
0
    return false;
431
0
  }
432
433
  /* On all platforms quotes are needed to preserve whitespace.  */
434
537k
  if (Shell_CharIsWhitespace(c)) {
435
0
    return true;
436
0
  }
437
438
537k
  if (flags & Shell_Flag_Fastbuild) {
439
0
    return false;
440
0
  }
441
442
  /* Quote hyphens in response files */
443
537k
  if (flags & Shell_Flag_IsResponse) {
444
0
    if (c == '-') {
445
0
      return true;
446
0
    }
447
0
  }
448
449
537k
  if (flags & Shell_Flag_IsUnix) {
450
    /* On UNIX several special characters need quotes to preserve them.  */
451
537k
    if (Shell_CharNeedsQuotesOnUnix(c)) {
452
0
      return true;
453
0
    }
454
537k
  } else {
455
    /* On Windows several special characters need quotes to preserve them.  */
456
0
    if (Shell_CharNeedsQuotesOnWindows(c) ||
457
0
        (c == ';' && (flags & Shell_Flag_VSIDE))) {
458
0
      return true;
459
0
    }
460
0
  }
461
537k
  return false;
462
537k
}
463
464
cm::string_view::iterator cmOutputConverter::Shell_SkipMakeVariables(
465
  cm::string_view::iterator c, cm::string_view::iterator end)
466
0
{
467
0
  while ((c != end && (c + 1) != end) && (*c == '$' && *(c + 1) == '(')) {
468
0
    cm::string_view::iterator skip = c + 2;
469
0
    while ((skip != end) && Shell_CharIsMakeVariableName(*skip)) {
470
0
      ++skip;
471
0
    }
472
0
    if ((skip != end) && *skip == ')') {
473
0
      c = skip + 1;
474
0
    } else {
475
0
      break;
476
0
    }
477
0
  }
478
0
  return c;
479
0
}
480
481
/*
482
Allowing make variable replacements opens a can of worms.  Sometimes
483
they should be quoted and sometimes not.  Sometimes their replacement
484
values are already quoted or contain escapes.
485
486
Some Visual Studio variables cause problems.  In order to pass the
487
referenced value with spaces the reference must be quoted.  If the
488
variable value ends in a backslash then it will escape the ending
489
quote!  In order to make the ending backslash appear we need this:
490
491
  "$(InputDir)\"
492
493
However if there is not a trailing backslash then this will put a
494
quote in the value so we need:
495
496
  "$(InputDir)"
497
498
This macro decides whether we quote an argument just because it
499
contains a make variable reference.  This should be replaced with a
500
flag later when we understand applications of this better.
501
*/
502
#define KWSYS_SYSTEM_SHELL_QUOTE_MAKE_VARIABLES 0
503
504
bool cmOutputConverter::Shell_ArgumentNeedsQuotes(cm::string_view in,
505
                                                  int flags)
506
41.3k
{
507
  /* The empty string needs quotes.  */
508
41.3k
  if (in.empty()) {
509
0
    return true;
510
0
  }
511
512
  /* Scan the string for characters that require quoting.  */
513
41.3k
  for (cm::string_view::iterator cit = in.begin(), cend = in.end();
514
579k
       cit != cend; ++cit) {
515
    /* Look for $(MAKEVAR) syntax if requested.  */
516
537k
    if (flags & Shell_Flag_AllowMakeVariables) {
517
#if KWSYS_SYSTEM_SHELL_QUOTE_MAKE_VARIABLES
518
      cm::string_view::iterator skip = Shell_SkipMakeVariables(cit, cend);
519
      if (skip != cit) {
520
        /* We need to quote make variable references to preserve the
521
           string with contents substituted in its place.  */
522
        return true;
523
      }
524
#else
525
      /* Skip over the make variable references if any are present.  */
526
0
      cit = Shell_SkipMakeVariables(cit, cend);
527
528
      /* Stop if we have reached the end of the string.  */
529
0
      if (cit == cend) {
530
0
        break;
531
0
      }
532
0
#endif
533
0
    }
534
535
    /* Check whether this character needs quotes.  */
536
537k
    if (Shell_CharNeedsQuotes(*cit, flags)) {
537
0
      return true;
538
0
    }
539
537k
  }
540
541
  /* On Windows some single character arguments need quotes.  */
542
41.3k
  if (flags & Shell_Flag_IsUnix && in.size() == 1) {
543
0
    char c = in[0];
544
0
    if ((c == '?') || (c == '&') || (c == '^') || (c == '|') || (c == '#')) {
545
0
      return true;
546
0
    }
547
0
  }
548
549
  /* UNC paths in MinGW Makefiles need quotes.  */
550
41.3k
  if ((flags & Shell_Flag_MinGWMake) && (flags & Shell_Flag_Make)) {
551
0
    if (in.size() > 1 && in[0] == '\\' && in[1] == '\\') {
552
0
      return true;
553
0
    }
554
0
  }
555
556
41.3k
  return false;
557
41.3k
}
558
559
std::string cmOutputConverter::Shell_GetArgument(cm::string_view in, int flags)
560
41.3k
{
561
  /* Output will be at least as long as input string.  */
562
41.3k
  std::string out;
563
41.3k
  out.reserve(in.size());
564
565
  /* Keep track of how many backslashes have been encountered in a row.  */
566
41.3k
  int windows_backslashes = 0;
567
568
  /* Whether the argument must be quoted.  */
569
41.3k
  int needQuotes = Shell_ArgumentNeedsQuotes(in, flags);
570
41.3k
  if (needQuotes) {
571
    /* Add the opening quote for this argument.  */
572
0
    if (flags & Shell_Flag_WatcomQuote) {
573
0
      if (flags & Shell_Flag_IsUnix) {
574
0
        out += '"';
575
0
      }
576
0
      out += '\'';
577
0
    } else {
578
0
      out += '"';
579
0
    }
580
0
  }
581
582
  /* Scan the string for characters that require escaping or quoting.  */
583
41.3k
  for (cm::string_view::iterator cit = in.begin(), cend = in.end();
584
579k
       cit != cend; ++cit) {
585
    /* Look for $(MAKEVAR) syntax if requested.  */
586
537k
    if (flags & Shell_Flag_AllowMakeVariables) {
587
0
      cm::string_view::iterator skip = Shell_SkipMakeVariables(cit, cend);
588
0
      if (skip != cit) {
589
        /* Copy to the end of the make variable references.  */
590
0
        while (cit != skip) {
591
0
          out += *cit++;
592
0
        }
593
594
        /* The make variable reference eliminates any escaping needed
595
           for preceding backslashes.  */
596
0
        windows_backslashes = 0;
597
598
        /* Stop if we have reached the end of the string.  */
599
0
        if (cit == cend) {
600
0
          break;
601
0
        }
602
0
      }
603
0
    }
604
605
    /* Check whether this character needs escaping for the shell.  */
606
537k
    if (flags & Shell_Flag_IsUnix) {
607
      /* On Unix a few special characters need escaping even inside a
608
         quoted argument.  */
609
537k
      if (*cit == '\\' || *cit == '"' || *cit == '`' || *cit == '$') {
610
        /* This character needs a backslash to escape it.  */
611
0
        out += '\\';
612
0
      }
613
537k
    } else if (flags & Shell_Flag_EchoWindows) {
614
      /* On Windows the built-in command shell echo never needs escaping.  */
615
0
    } else {
616
      /* On Windows only backslashes and double-quotes need escaping.  */
617
0
      if (*cit == '\\') {
618
        /* Found a backslash.  It may need to be escaped later.  */
619
0
        ++windows_backslashes;
620
0
      } else if (*cit == '"') {
621
        /* Found a double-quote.  Escape all immediately preceding
622
           backslashes.  */
623
0
        while (windows_backslashes > 0) {
624
0
          --windows_backslashes;
625
0
          out += '\\';
626
0
        }
627
628
        /* Add the backslash to escape the double-quote.  */
629
0
        out += '\\';
630
0
      } else {
631
        /* We encountered a normal character.  This eliminates any
632
           escaping needed for preceding backslashes.  */
633
0
        windows_backslashes = 0;
634
0
      }
635
0
    }
636
637
    /* Check whether this character needs escaping for a make tool.  */
638
537k
    if (*cit == '$') {
639
0
      if (flags & Shell_Flag_Make) {
640
        /* In Makefiles a dollar is written $$.  The make tool will
641
           replace it with just $ before passing it to the shell.  */
642
0
        out += "$$";
643
0
      } else if (flags & Shell_Flag_VSIDE) {
644
        /* In a VS IDE a dollar is written "$".  If this is written in
645
           an un-quoted argument it starts a quoted segment, inserts
646
           the $ and ends the segment.  If it is written in a quoted
647
           argument it ends quoting, inserts the $ and restarts
648
           quoting.  Either way the $ is isolated from surrounding
649
           text to avoid looking like a variable reference.  */
650
0
        out += "\"$\"";
651
0
      } else if (flags & Shell_Flag_Fastbuild) {
652
0
        out += "^$";
653
0
      } else {
654
        /* Otherwise a dollar is written just $. */
655
0
        out += '$';
656
0
      }
657
537k
    } else if (*cit == '#') {
658
0
      if ((flags & Shell_Flag_Make) && (flags & Shell_Flag_WatcomWMake)) {
659
        /* In Watcom WMake makefiles a pound is written $#.  The make
660
           tool will replace it with just # before passing it to the
661
           shell.  */
662
0
        out += "$#";
663
0
      } else {
664
        /* Otherwise a pound is written just #. */
665
0
        out += '#';
666
0
      }
667
537k
    } else if (*cit == '%') {
668
0
      if ((flags & Shell_Flag_VSIDE) ||
669
0
          ((flags & Shell_Flag_Make) &&
670
0
           ((flags & Shell_Flag_MinGWMake) || (flags & Shell_Flag_NMake)))) {
671
        /* In the VS IDE, NMake, or MinGW make a percent is written %%.  */
672
0
        out += "%%";
673
0
      } else {
674
        /* Otherwise a percent is written just %. */
675
0
        out += '%';
676
0
      }
677
537k
    } else if (*cit == ';') {
678
0
      if (flags & Shell_Flag_VSIDE) {
679
        /* In VS a semicolon is written `";"` inside a quoted argument.
680
           It ends quoting, inserts the `;`, and restarts quoting.  */
681
0
        out += "\";\"";
682
0
      } else {
683
        /* Otherwise a semicolon is written just ;. */
684
0
        out += ';';
685
0
      }
686
537k
    } else if (*cit == '\n') {
687
0
      if (flags & Shell_Flag_Ninja) {
688
0
        out += "$\n";
689
0
      } else {
690
0
        out += '\n';
691
0
      }
692
537k
    } else if (*cit == '^' && (flags & Shell_Flag_Fastbuild)) {
693
0
      out += "^^";
694
537k
    } else {
695
      /* Store this character.  */
696
537k
      out += *cit;
697
537k
    }
698
537k
  }
699
700
41.3k
  if (needQuotes) {
701
    /* Add enough backslashes to escape any trailing ones.  */
702
0
    while (windows_backslashes > 0) {
703
0
      --windows_backslashes;
704
0
      out += '\\';
705
0
    }
706
707
    /* Add the closing quote for this argument.  */
708
0
    if (flags & Shell_Flag_WatcomQuote) {
709
0
      out += '\'';
710
0
      if (flags & Shell_Flag_IsUnix) {
711
0
        out += '"';
712
0
      }
713
0
    } else {
714
0
      out += '"';
715
0
    }
716
0
  }
717
718
41.3k
  if (flags & Shell_Flag_UnescapeNinjaConfiguration) {
719
0
    std::string ninjaConfigReplace;
720
0
    if (flags & Shell_Flag_IsUnix) {
721
0
      ninjaConfigReplace += '\\';
722
0
    }
723
0
    ninjaConfigReplace += "$${CONFIGURATION}";
724
0
    cmSystemTools::ReplaceString(out, ninjaConfigReplace, "${CONFIGURATION}");
725
0
  }
726
727
41.3k
  return out;
728
41.3k
}